线程池遇到的问题总结

JAVA

Posted by 朱坤乾 on March 31, 2018

简单线程池原理

这只是我写线程池超时处理遇到的小问题.具体请阅读源码,或者相关书籍进行了解线程池原理 我们也可以使用Spring配置线程池来使用

1 首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
2 其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。 3 最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务

本人简单理解: 第一次,先判断当前的核心线程数, 如果小于初始化的值,马上创建;然后第二个if,将这个任务插入到工作线程,双重判断任务,假定如果前面不能直接加入到线程池Worker集合里,则加入到workQueue队列等待执行。 里面的if else判断语句则是检查当前线程池的状态。如果线程池本身的状态是要关闭并清理了, 我们则不能提交线程进去了。这里我们就要reject他们。

Executors创建的四种线程池

newFixedThreadPool(int Threads):创建固定数目的线程池。
newSingleThreadPoolExecutor():创建一个单线程化的Executor
newCacheThreadPool():创建一个可缓存的线程池,调用execute将重用以前构成的线程(如果线程可用)。
如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移出那些已有60秒钟未被使用的线程。
newScheduledThreadPool(int corePoolSize)创建一个支持定时及周期性的任务执行的线程池,
多数情况下可用来替代Time类。

但是在阿里巴巴java开发手册中明确指出,不允许使用Executors创建线程池。

创建线程池的正确方法:

其实当了解线程池原理后,我们可以自己直接调用ThreadPoolExecutor的构造函数自己创建线程池。 在创建的同时,给BlockQueue指定容量大的容量就可以了。可以保证所有线程正常进行,不采用拒绝策略

ThreadPoolExecutor executor = new ThreadPoolExecutor
(int corePoolSize, 
int maximumPoolSize, 
long keepAliveTime, 
TimeUnit unit, 
BlockingQueue<Runnable> workQueue, 
ThreadFactory threadFactory, 
RejectedExecutionHandler handler)

构造方法参数说明 1:corePoolSize 核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。 除非将allowCoreThreadTimeOut设置为true。 2:maximumPoolSize 线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。 3:keepAliveTime 非核心线程的闲置超时时间,超过这个时间就会被回收。 4:unit 指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。 5:workQueue 线程池中的任务队列. 常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。 6:threadFactory 线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法

public interface ThreadFactory {
  Thread newThread(Runnable r);
}

7RejectedExecutionHandler 通过线程工厂可以对线程的一些属性进行定制。 RejectedExecutionHandler也是一个接口,只有一个方法 当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。

抛弃政策原理;:::::

每个变量的作用都已经标明出来了,这里要重点解释一下corePoolSize、maximumPoolSize、largestPoolSize三个变量。 corePoolSize在很多地方被翻译成核心池大小,其实我的理解这个就是线程池的大小。举个简单的例子: 假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。 因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做; 当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待; 如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来; 然后就将任务也分配给这4个临时工人做; 如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。 当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。

这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。 也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。 不过为了方便理解,在本文后面还是将corePoolSize翻译成核心池大小。 largestPoolSize只是一个用来起记录作用的变量,用来记录线程池中曾经有过的最大线程数目,跟线程池的容量没有任何关系。

线程池做超时监听处理

ThreadPoolExecutor executor = new ThreadPoolExecutor("","","");//填海需求
FutureTask<String> future =
       new FutureTask<String>(new Callable<String>() {//使用Callable接口作为构造参数
         public String call() {
           //真正的任务在这里执行,这里的返回值类型为String,可以为任意类型
       }});
executor.execute(future);
//在这里可以做别的任何事情
try {
	result = future.get(5000, TimeUnit.MILLISECONDS); //取得结果,同时设置超时执行时间为5秒。同样可以用future.get(),不设置执行超时时间取得结果
} catch (InterruptedException e) {
	futureTask.cancel(true);
} catch (ExecutionException e) {
	futureTask.cancel(true);
} catch (TimeoutException e) {
	futureTask.cancel(true);
} finally {
	//做一些逻辑处理
}

executor.submit和executor.execute方法区别

1:submit有返回值,而execute没有.

2:execute方法位于接口Executor中。submit方法位于AbstractExecutorService中。

3:submit最终也是在调用execute方法,无论是Runnable还是Callable类型接口,都会被封装成FutureTask继续执行。 如果使用submit方法提交,会进一步封装成FutureTask,执行execute方法,在FutureTask里面重写的run方法里面调用Callable接口的call方法。

futureTask.cancel(true);

    * 当你想要取消你已提交给执行者的任务,使用Future接口的cancel()方法。
    * 根据cancel()方法参数和任务的状态不同,这个方法的行为将不同:
    *      1、如果这个任务已经完成或之前的已经被取消或由于其他原因不能被取消,
    *          那么这个方法将会返回false并且这个任务不会被取消。
    *      2、如果这个任务正在等待执行者获取执行它的线程,那么这个任务将被取消而且不会开始他的执行。
    *          如果这个任务已经正在运行,则视方法的参数情况而定。
    *          cancel()方法接收一个Boolean值参数。
    *          如果参数为true并且任务正在运行,那么这个任务将被取消。
    *          如果参数为false并且任务正在运行,那么这个任务将不会被取消。

futureTask.cancel(true)只是将线程的状态改变成中断(Interrupted),源码中就是调用了interrupt()方法,该方法不能中断在运行中的线程,它只能改变中断状态而已。 但是如果子线程现在正处于死循环的状态.那么该线程将会处于死循环状态. 解决办法,我们仿照Stop()优化方法,死循环一般为while(true){} 所以我们DEBUG找到死循环具体位置

while(true){
		  //TODO  就是这里进行的死循环,监控while循环,然后加一个判断
          if (Thread.currentThread().isInterrupted()) { // 任务被取消
          System.out.println("PrimerTask.call: 你取消我干啥?");
          flag="error";
          return ;
          }
}

这样就可以退出该线程.进行下一个线程处理

第二种方法

我们根据自定义线程名字获取到该线程的,然后调用Stop()方法也可以. 但是Stop()方法已经被禁用.安全问题太大,详情自行百度.

实现如下

ThreadGroup currentGroup = 
                Thread.currentThread().getThreadGroup();
 //此线程组中活动线程的估计数
                int noThreads = currentGroup.activeCount();
                Thread[] lstThreads = new Thread[noThreads];
//把对此线程组中的所有活动子组的引用复制到指定数组中。
                currentGroup.enumerate(lstThreads);
                for (int i = 0; i < noThreads; i++){
//                    logger.info("线程号:" + i + " = " + lstThreads[i].getName());
                if("Threadstophaha".equals(lstThreads[i].getName())){
                    lstThreads[i].stop();
                }
             }

这只是我写线程池调用时遇到的小问题,请阅读源码,或者相关书籍进行了解线程池原理