楼子湾

导航

 

ThreadPoolExecute一共有四种构造函数:如下

参数含义
corePoolSize 核心线程池大小
maximumPoolSize 线程池最大容量大小
keepAliveTime 线程池空闲时,线程存活的时间
TimeUnit 线程活动保持时间的单位
BlockingQueue<Runnable> 任务队列,用于保存等待执行的任务的阻塞队列
ThreadFactory 用于设置线程的工厂
RejectedExecutionHandler 饱和策略

corePoolSize:线程池中最少的线程数,一个项目组总得有corePoolSize人坚守阵地,都是签订劳动合同了,不能随便撤。

maximumPoolSize:当项目很忙时,就得加人,请其他项目组的人来帮忙。但是公司空间有限,最多只能加到maximumPoolSize个人。当项目闲了,就得撤人了,最多能撤到corePoolSize个人

keepAliveTime & unit:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?如果一个线程在keepAliveTime(时间数字)* unit(时间单位)时间内都没有执行任务,说明这个线程很闲。如果此时线程数大于corePoolSize,这个线程就要被回收了

workQueue:就是任务队列

threadFactory:自定义如果创建线程,例如给线程指定一个有意义的名字

handler:workQueue满了(排期满了),再提交任务,该怎么处理呢?这个就是处理策略,线程池提供了4种策略,你也可以实现RejectedExecutionHandler接口来自定义策略

策略
AbortPolicy 丢弃任务,抛运行时异常(默认的处理策略)
CallerRunsPolicy 执行任务
DiscardPolicy 忽视,什么都不会发生
DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务

线程池的工作流程

可以参照一下源码理解一下下面的流程

1.线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行他们。

2.当调用execute()方法添加一个任务时,线程池会做如下判断:

a. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务

b. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列

c. 如果这时候队列满了,而且正在运行的线程数量小于maximunPoolSize,那么还是要创建非核心线程立刻运行这个任务

d. 如果队列满了,而且正在运行的线程数量大于或等于maximunPoolSize,那么线程池会抛出RejectedExecutionException

3.当一个线程完成任务时,它会从队列中取下一个任务来执行

4.当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小

可以用如下图来表示整体流程

 

这里以jdk1.8.0_111的源代码为例,看一下具体实现。
1、先看一下线程池的executor方法
①:判断当前活跃线程数是否小于corePoolSize,如果小于,则调用addWorker创建线程执行任务
②:如果不小于corePoolSize,则将任务添加到workQueue队列。
③:如果放入workQueue失败,则创建线程执行任务,如果这时创建线程失败(当前线程数不小于maximumPoolSize时),就会调用reject(内部调用handler)拒绝接受任务。
 
2、再看下addWorker的方法实现
这块代码是在创建非核心线程时,即core等于false。判断当前线程数是否大于等于maximumPoolSize,如果大于等于则返回false,即上边说到的③中创建线程失败的情况。
 
addWorker方法的下半部分:
①创建Worker对象,同时也会实例化一个Thread对象。
②启动启动这个线程
 
3、再到Worker里看看其实现
可以看到在创建Worker时会调用threadFactory来创建一个线程。上边的②中启动一个线程就会触发Worker的run方法被线程调用。
 
4、接下来咱们看看runWorker方法的逻辑
线程调用runWoker,会while循环调用getTask方法从workerQueue里读取任务,然后执行任务。只要getTask方法不返回null,此线程就不会退出。
 
5、最后在看看getTask方法实现
①咱们先不管allowCoreThreadTimeOut,这个变量默认值是false。wc>corePoolSize则是判断当前线程数是否大于corePoolSize。
②如果当前线程数大于corePoolSize,则会调用workQueue的poll方法获取任务,超时时间是keepAliveTime。如果超过keepAliveTime时长,poll返回了null,上边提到的while循序就会退出,线程也就执行完了。
如果当前线程数小于corePoolSize,则会调用workQueue的take方法阻塞在当前。
 

到这里其实会有人疑问,那线程池具体使如何运行的

  1.线程池在启动的时候就开始运行了,只不过一开始并没有线程也没有任务,当第一个任务进来时开开始创建核心线程

  2.线程如何开始运行工作的,集中在Woker类上

  Worker类

  Worker类就是线程池中执行任务的类,主要源码和解释如下图:

  所以Worker本身就是一个Runnable,它有两个属性thead、firstTask;那我们就可以来梳理一下整体的运行流程了:

  线程池调用execute—>创建Worker(设置属性thead、firstTask)—>worker.thread.start()—>实际上调用的是worker.run()—>线程池的runWorker(worker)—>worker.firstTask.run()(如果firstTask为null就从等待队列中拉取一个)。

  转了一大圈最终调用最开始传进来的任务的run方法,不过通过等待队列可以重复利用worker与worker中的线程,变化的只是firstTask;

  总结

  •     线程池的execute的作用是把任务放到等待队列中或者新建worker并把任务放到worker的firstTask,最后执行worker中的thread;
  •     Worker中的thread的start方法会执行Worker的run方法;
  •     Worker的run方法会调用线程池的runWorker方法;
  •     runWorker方法则是调用worker的firstTask的run方法,达到目的;
  •     好处就是可以重复利用Worker与Worker中的thread,这也是线程池的优势。

  3.当活跃线程(线程池中存在的线程)数小于核心线程数,此时依然是创建核心线程

  4.当已经达到核心线程,但是小于最大线程,任务就以Woker添加到工作队列,getTask()方法会一直从线程池中获取任务处理

    这里如果队列没满,依然是以核心线程处理

  5.达到核心线程,工作队列慢,还没达到最大线程,就创建新的线程

  6.随着任务的处理,工作线程就限制下来,超过核心线程数的线程是如何销毁的: getTask方法(线程池如何实现无用的非核心线程销毁操作的)
  • 判断是否允许核心线程超时或者当前活动线程数是否大于核心线程数(allowCoreThreadTimeOut默认为false)
  • boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
  • 判断当前活动线程数wc数是否大于最大线程数 || 允许超时 && 上次超时获取到的任务为null,接着判断如果wc>1,或者队列为空,则将工作线程数wc减1,并且返回一个null的task(此处就是判断是否为无用的线程的,如果无用,则在runWorker的processWorkerExit(w, completedAbruptly);这一行代码会把当前worker对象给销毁)
  • if ((wc > maximumPoolSize || (timed && timedOut))
                    && (wc > 1 || workQueue.isEmpty())) {
                    if (compareAndDecrementWorkerCount(c))
                        return null;
                    continue;
                }

     

  • 根据timed来判断workQueue是超时等待获取队列任务,还是一直阻塞等待任务。

  超时等待:当超过给定keepAliveTime时间还没有获取到任务时,则会返回null,此时Woker会被销毁

  阻塞等待:一直阻塞,直到有任务进来

 
 
 
 
 
posted on 2021-01-18 14:19  楼子湾  阅读(521)  评论(0)    收藏  举报