经过了层层波折,总算是把addWorker大致走了下来,那么addWorker的返回值是返回到execute方法里的,为了方便阅读我们再把execute方法源码放在下面:
|
|
先前第一次提到这个方法时就是分析到第五行代码才扯到复杂的addWorker方法里的,大概了解了addWorker方法的逻辑后,现在我们再回来看execute方法。
通过ctl的get方法获得可进行原子操作的整数c后,通过workerCountOf方法获取到c中存储的工作线程数并与corPoolSize进行比较,如果小于corePoolSize就直接进行addWorker(command,true)操作,经过前面的分析我们已经知道,这里传入的true表示在addWorker方法前半部分第二层for循环中工作线程数会与corePoolSize(而不是maximumPoolSize)进行比较,小于后者后将command作为参数初始化一个Worker对象和对象中的线程,并将这个Worker添加进workers的hashmap,最后运行command的run方法,而addWorker成功后,也就会直接返回一个true值,execute方法也就结束了。
也就是说,①如果当前工作线程数小于corePoolSize,新建的任务会直接启动一条新线程,并立刻执行当前任务。
execute方法当然不可能这么简单,接下来第二种情况,在一段时间的execute操作后,当前运行的工作线程数已经超过了corePoolSize,于是代码跳过小于corePoolSize的if语句,进入下面这段代码:
|
|
判断当前线程池状态为RUNNING且workQueue.offer(command)返回值为true才能进入if代码段,workQueue是一个BlockingQueue对象,此处用作任务队列,workQueue.offer自然也就是向任务队列中添加Runnable任务对象,而offer方法的特性就是添加成功后返回true,失败返回false,所以如果线程池为RUNNING且向任务队列中成功添加Runnable任务,就会运行到if内的代码段。
if内的代码先不说,if这个判断条件是一定会运行到的,也就是说,②如果当前工作线程数大于等于corePoolSize,新建的任务不会直接开启新线程执行,而是暂时存放到任务队列中。
而有些任务队列是有长度限制的,如果添加的任务数量超过了任务队列的长度,此时任务队列就会拒绝继续添加任务,workQueue.offer方法就会返回false,代码也就会执行到execute方法最后的else if代码:
|
|
else if中又进行了addWorker操作,但是这次第二个参数传入了false,根据前面对addWorker方法的分析,我们可以思考出,此后addWorker方法中在通过第一层for循环确认线程池的运行状态后,在第二层将当前工作线程数直接与maximumPoolSize进行了比较,在大于corePoolSize且小于maximumPoolSize的情况下再次使工作线程数加一,然后进入addWorker方法的后半部分,实例化一个Worker对象并运行线程执行这个Worker对象的run方法。
也就是说,③在当前运行的工作线程数大于等于corePoolSize,且任务队列也满了的情况下,新建的任务会再次开启新线程来执行,直到开启的新线程数达到maximumPoolSize,线程池就不再接收任务。
至此execute也就走完了,execute的整个过程也就是线程池处理线程的策略,总结出来就是上面标号加粗的三点,这里综合一下放在下面:
- 如果当前工作线程数小于corePoolSize,新建的任务会直接启动一条新线程,并立刻执行当前任务;
- 如果当前工作线程数大于等于corePoolSize,而任务队列没有满,则新建的任务不会直接开启新线程执行,而是暂时存放到任务队列中;
- 在当前运行的工作线程数大于等于corePoolSize,且任务队列也满了的情况下,新建的任务会再次开启新线程来执行,直到开启的新线程数达到maximumPoolSize,线程池就不再接收任务
作为一个健壮的框架,新建的线程当然不能就那么放在那不管了,对线程的复用和回收,线程池在getTask方法里执行了一定的策略。
根据前面整个流程的分析我们知道,需要新开启线程时会调用addWorker方法,addWorker方法在两层for循环为工作线程数+1后会初始化一个worker对象并添加到workers集合里,最后调用这个worker对象内部线程的run方法开启一条新线程,而Worker类的run方法会调用ThreadPoolExecutor的runWorker方法,runWorker方法会将传入的非null的task执行完后,通过getTask方法不断从任务队列中取任务执行,getTask方法前面已经简单的分析过,但有一点没有说明,这里再将getTask方法放在下面:
|
|
正如在代码中标注的内容所示,timed被用来标识当前worker是否超时退出,这是怎么实现的呢,我们看到timed的赋值是由两个条件相或得到的,一个是allowCoreThreadTimeOut,一个是wc>corePoolSize。allowCoreThreadTimeOut是一个波尔值,在没有手动调用allowCoreThreadTimeOut()方法赋值的情况下默认是false,所以默认情况下是通过wc > corePoolSize赋值,如果当前工作线程数大于corePoolSize则timed为true,小于corePoolSize则timed为false。
用到timed的地方是最后的try代码块中:
|
|
可以看到,根据timed的取值,选择了两种从任务队列中取任务的方式,分别是poll和take,两个方法都是从队列首位取对象,但是对取不出对象的策略不同,poll方法如果不能立即取到对象,会等待传入的keepAliveTime时间,如果超过了keepAliveTime还没有取到对象就返回null,而take方法如果取不到对象,就会使当前线程阻塞直到队列中出现对象可以取出为之。
也就是说,通过对任务队列取任务的不同方式的处理实现了线程保活的策略,如果工作线程数超过了corePoolSize,说明当前线程不是核心线程,于是调用poll方法取任务,在任务队列中没任务可取时,就会保活keepAliveTime时间,keepAliveTime时间之后还没有任务可以执行这个线程就会退出;而如果工作线程数没有超过corePoolSize,说明当前的这条线程属于核心线程,所以会使用take方法取任务,就算任务队列中没有任务可取,当前线程也会阻塞,不会被回收。如此就实现了对核心线程和核心线程外的线程不同的保活和回收策略。