JDK并发包下面的线程池是面试中常常被考查的点,以前我写过一篇ThreadPoolExecutor源码分析的文章。由于篇幅有限当时没说面试中常见的考查点和哪些点是应该掌握。那篇文章着实有点长,更合适用电脑看,结合源码看。今天,我来谈谈本身以为ThreadPoolExecutor哪些点是应该掌握的,这些点应该掌握的点正是面试中常常被问的东西。如今抛出几个问题,若是你都能答上来,能够不用往下面看啦。java
若是回答的上就pass吧,哈哈面试
corePoolSize
线程池中的核心线程数mmaximumPoolSize
线程池中的最大线程数keepAliveTime
当线程池中线程数量超过corePoolSize时,容许等待多长时间从workQueue中拿任务unit
keepAliveTime 对应的时间单位,为TimeUnit类。workQueue
阻塞队列,当线程池中线程数超过corePoolSize时,用于存储提交的任务。threadFactory
线程池采用,该线程工厂建立线程池中的线程。handler
为RejectedExecutionHandler,当线程线中线程超过maximumPoolSize时采用的,拒绝执行处理器。
简单介绍一下,一个任务提交给线程池后,线程池建立线程来执行提交任务的流程。
一、当提交任务时线程池中的来用执行任务的线程数小于corePoolSize(核心线程数),则线程池利用ThreadFacory(线程工厂)建立线程用于执行提交的任务。不然执行第二2步。
二、当提交任务时线程池中的来用执行任务的线程数大于corePoolSize(核心线程数),但workQueue没有满,则线程池会将提交的任务先保存在workQueue(工做队列),等待线程池中的线程执行完其它已提交任务后会循环从workQueue中取出任务执行。不然执行第3步。
三、当提交任务时线程池中的来用执行任务大于corePoolSize(核心线程数),且workQueu已满,但没有超过maximunPoolSize(最大线程数),则线程池利用ThreadFacory(线程工厂)建立线程用于执行提交的任务。不然执行4。
四、当提交任务时线程池中的来用执行任务大于maximunPoolSize,执行线程池中配置的拒绝策略(RejectedExecutionHanlder)。
因此在设置ThreadPoolExecutor的参数时必定要特别当心,不建议采用很大的ArrayBlockQueue或不限大小的LinkedBlockQueue,同时corePoolSize也不该该设置过大。CUP密集的任务的话能够设置小一点(CUP数据+1这种)避免没必要要的上下文切换;而对于IO密集的任务则corePoolSize则能够设置的大一点,能够避免长时间IO等待而CUP却空闲。threadFactory建议采用本身定义的,让其建立的线程容易区分,方便问题定位。并发
ThreadPoolExecutor中的线程哪一个时间点被建立?是任务提交后吗?能够在任务提交前建立吗?
通常在任务被提交后,线程池会利用线程工厂去建立线程,但当线程池中线程数已为corePoolSize时或maxmumPoolSize时不会。能够在任务提交前经过prestartCoreThread方法或prestartAllCoreThreads方法预先建立核心线程。具体能够参考这下这个图:ide
线程池中线程实现是在addWorker方法中被建立的,详见以前文章中addWorker方法分析。建立后完,该线程就被启动。线程池中被建立的线程被封装到了Worker对象中,而Worker类又实现了Runnable接口,线程池中的线程又引用了worker。当线程被start后实际就有机会等待操做系统调度执行Worker类的run方法。源码分析
Worker(Runnable firstTask) { setState(-1); this.firstTask = firstTask; //建立的线程引用了worker this.thread = getThreadFactory().newThread(this); }
一旦线程池经过ThreadFactory建立好线程后,就会将建立的线程封装到了Worker对象中,同时启动该线程。新建立的线程会执行刚提交的任务,同时会不断地从workerQueue中取出任务执行。线程池的线程复用正是经过不断地从workerQueue中取出任务来执行达到的。源码分析见runWorkers方法分析。this
同时一时刻不能执行多个任务,只有一个任务执行完时才能去执行另外一个任务。上面说到线程池中经过ThreadFacory建立的线程最后会被封装到Worker中,而该线程又引用了Worker,start线程后,任务实际上是在Worker中的run方法中被执行,最终run又将任务执行代理给ThreadPoolExecutor的runWorker方法。操作系统
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {...}
Worder一方面实现了Runnable,另外一方面又继承了AQS。经过实现AQS,Worker具备了排它锁的语义,每次在执行提交任务时都会先lock操做,执行完任务后再作unlock操做。正是这个加锁与解锁的操做,保证了同一个线程要执行完当前任务才有机再去执行另外一个任务。.net
shutdown方法是将线程池的状态设置为SHUTDOWN,此时新任务不能被提交(提交会抛出异常),workerQueue的任务会被继续执行,同时线程池会向那些空闲的线程发出中断信号。空闲的线程实际就不没在执行任务的线程。如何被封装在worker里的线程能加锁,这里这个线程实现会就空闲的。下面是向空闲的线程发出中断信号源码。线程
private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; //w.tryLock()用于加锁,看线程是否在执行任务 if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
shutdownNow方法是将线程池的状态设置为STOP,此时新任务不能被提交(提交会抛出异常),线程池中全部线程都会收到中断的信号。具体线程会做出什么响应,要看状况,若是线程由于调用了Object的wait、join方法或是自身的sleep方法而阻塞,那么中断状态会被清除,同时抛出InterruptedException。其它状况能够参考Thread.interrupt方法的说明。shutdownNow方法向全部线程发出中断信息源码以下:代理
private void interruptWorkers() { final ReentrantLock mainLock = this.mainLock; //加锁操做保证中断过程当中不会新woker被建立 mainLock.lock(); try { for (Worker w : workers) w.interruptIfStarted(); } finally { mainLock.unlock(); } }
若是没指核心线程容许超时将会有问题。核心线程容许超时是指在从wokerQueue中获取任务时,采用的阻塞的获取方式等待任务到来,仍是经过设置超时的方式从同步阻塞队列中获取任务。便是统统过BlockingQueue的poll方法获取任务仍是take方法获取任务。可参考以前的源码分析中的getTask方法分析。若是不调用shutdown或shutdownNow方法,核心线程因为在getTask方法调用BlockingQueue.take方法获取任务而处于一直被阻塞挂起状态。核心线程将永远处于Blocking的状态,致使内存泄漏,主线程也没法退出,除非强制kill。试着运行以下程序会发现,程序没法退出。
public class Test { public static void main(String args[]) { ExecutorService executorService = new ThreadPoolExecutor(3, 3,10L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10)); executorService.submit(new Runnable() { @Override public void run() { System.out.println("thread name " + Thread.currentThread().getName()); } }); } }
所在使用线程池时必定要记得根本具体场景调用shutdown或shutdownNow方法关闭线程池。shutdown方法适用于提交任务都要被执行完的场景,shutdownNow方法适用于不关心提交任务是否执行完的场景。
线程池提供了三个扩展点,分别是提交任务的run方法或是call方法被调用前与被调后,即beforeExecutor与afaterExecutor方法;另一个扩展点是线程池的状态从TIDYING状态流转为TERMINATED状态时terminated方法会被调用。
原本只是想写一点点,写着写着就发现又有点长。这篇主要是介绍了ThreadPoolExecutor中我的认为比较重要点,同时也是把ThreadPoolExecutor再梳理一下发现本身以前理解有误差的地方。
若是你以为这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
点赞,转发,有大家的 『点赞和评论』,才是我创造的动力。
关注公众号 『 java烂猪皮 』,不按期分享原创知识。
同时能够期待后续文章ing🚀
做者:叶易
出处:club.perfma.com/article/191…