怎么才算掌握了JDK中的线程池

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JDK并发包下面的线程池是面试中经常被考查的点,之前我写过一篇"},{"type":"link","attrs":{"href":"https://club.perfma.com/article/1719588","title":null},"content":[{"type":"text","text":"ThreadPoolExecutor源码分析"}]},{"type":"text","text":"的文章。因为篇幅有限当时没说面试中常见的考查点和哪些点是应该掌握。那篇文章着实有点长,更合适用电脑看,结合源码看。今天,我来谈谈自己觉得ThreadPoolExecutor哪些点是应该掌握的,这些点应该掌握的点正是面试中经常被问的东西。现在抛出几个问题,如果你都能答上来,可以不用往下面看啦。"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中常用参数有哪些,作用是什么?任务提交后,ThreadPoolExecutor会按照什么策略去创建线程用于执行提交任务?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor有哪些状态,状态之间流转是什么样子的?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中的线程哪个时间点被创建?是任务提交后吗?可以在任务提交前创建吗?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中创建的线程哪个时间被启动?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor竟然是线程池那么他是如何做到重复利用线程的?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中创建的同一个线程同一时刻能执行多个任务吗?如果不能是通过什么机制保证ThreadPoolExecutor中的同一个线程只能执行完一个任务,才会机会去执行另一个任务?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中关闲线程池的方法shutdown与shutdownNow的区别是什么?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null},"content":[{"type":"text","text":"通过submit方法向ThreadPoolExecutor提交任务后,当所有的任务都执行完后不调用shutdown或shutdownNow方法会有问题吗?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":9,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor有没有提供扩展点,方便在任务执行前或执行后做一些事情?"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果回答的上就pass吧,哈哈"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor参数有哪些与创建线程策略?"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ThreadPoolExecutor参数"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"corePoolSize"}]},{"type":"text","text":" 线程池中的核心线程数"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"mmaximumPoolSize"}]},{"type":"text","text":" 线程池中的最大线程数"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"keepAliveTime"}]},{"type":"text","text":" 当线程池中线程数量超过corePoolSize时,允许等待多长时间从workQueue中拿任务"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"unit"}]},{"type":"text","text":" keepAliveTime 对应的时间单位,为TimeUnit类。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"workQueue"}]},{"type":"text","text":" 阻塞队列,当线程池中线程数超过corePoolSize时,用于存储提交的任务。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"threadFactory"}]},{"type":"text","text":" 线程池采用,该线程工厂创建线程池中的线程。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"handler"}]},{"type":"text","text":" 为RejectedExecutionHandler,当线程线中线程超过maximumPoolSize时采用的,拒绝执行处理器。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"创建线程策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/07/07fb9ec57f20d5d505261bc6af88236b.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"简单介绍一下,一个任务提交给线程池后,线程池创建线程来执行提交任务的流程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、当提交任务时线程池中的来用执行任务的线程数小于corePoolSize(核心线程数),则线程池利用ThreadFacory(线程工厂)创建线程用于执行提交的任务。否则执行第二2步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、当提交任务时线程池中的来用执行任务的线程数大于corePoolSize(核心线程数),但workQueue没有满,则线程池会将提交的任务先保存在workQueue(工作队列),等待线程池中的线程执行完其它已提交任务后会循环从workQueue中取出任务执行。否则执行第3步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、当提交任务时线程池中的来用执行任务大于corePoolSize(核心线程数),且workQueu已满,但没有超过maximunPoolSize(最大线程数),则线程池利用ThreadFacory(线程工厂)创建线程用于执行提交的任务。否则执行4。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、当提交任务时线程池中的来用执行任务大于maximunPoolSize,执行线程池中配置的拒绝策略(RejectedExecutionHanlder)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以在设置ThreadPoolExecutor的参数时一定要特别小心,不建议采用很大的ArrayBlockQueue或不限大小的LinkedBlockQueue,同时corePoolSize也不应该设置过大。CUP密集的任务的话可以设置小一点(CUP数据+1这种)避免不必要的上下文切换;而对于IO密集的任务则corePoolSize则可以设置的大一点,可以避免长时间IO等待而CUP却空闲。threadFactory建议采用自己定义的,让其创建的线程容易区分,方便问题定位。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"线程池有哪些状态,状态之间流转是什么样子的?"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RUNNING:运行中,接收新的任务或处理队列中的任务。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SHUTDOWN:关闭,不再接收新的任务,但会处理队列中的任务值为0。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"STOP:停止,不再接收新的任务,也不处理队列中的任务,并中断正在处理的任务。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TIDYING:所有任务已结束队列大小为0,转变TIDYING状态的线程将会执行terminated()方法。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TERMINATED:结束terminated()已被执行完。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"状态流程如下图:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1a/1a2185e358c91e9a33250727fed341c9.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"池程池中的线程哪个时间点被创建?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中的线程哪个时间点被创建?是任务提交后吗?可以在任务提交前创建吗?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般在任务被提交后,线程池会利用线程工厂去创建线程,但当线程池中线程数已为corePoolSize时或maxmumPoolSize时不会。可以在任务提交前通过prestartCoreThread方法或prestartAllCoreThreads方法预先创建核心线程。具体可以参考这下这个图:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bc/bcd257a31da9b57e1656e95392d5e53a.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor中创建的线程哪个时间被启动?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"线程池中线程实现是在addWorker方法中被创建的,详见之前文章中addWorker方法分析。创建后完,该线程就被启动。线程池中被创建的线程被封装到了Worker对象中,而Worker类又实现了Runnable接口,线程池中的线程又引用了worker。当线程被start后实际就有机会等待操作系统调度执行Worker类的run方法。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"Worker(Runnable firstTask) {\n setState(-1); \n this.firstTask = firstTask;\n //创建的线程引用了worker\n this.thread = getThreadFactory().newThread(this);\n}\n复制代码"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor竟然是线程池那么他是如何做到重复利用线程的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一旦线程池通过ThreadFactory创建好线程后,就会将创建的线程封装到了Worker对象中,同时启动该线程。新创建的线程会执行刚提交的任务,同时会不断地从workerQueue中取出任务执行。线程池的线程复用正是通过不断地从workerQueue中取出任务来执行达到的。源码分析见runWorkers方法分析。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor中创建的同一个线程同一时刻能执行多个任务吗?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同时一时刻不能执行多个任务,只有一个任务执行完时才能去执行另一个任务。上面说到线程池中通过ThreadFacory创建的线程最后会被封装到Worker中,而该线程又引用了Worker,start线程后,任务其实是在Worker中的run方法中被执行,最终run又将任务执行代理给ThreadPoolExecutor的runWorker方法。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"private final class Worker\n extends AbstractQueuedSynchronizer\n implements Runnable\n {...}\n复制代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Worder一方面实现了Runnable,另一方面又继承了AQS。通过实现AQS,Worker具有了排它锁的语义,每次在执行提交任务时都会先lock操作,执行完任务后再做unlock操作。正是这个加锁与解锁的操作,保证了同一个线程要执行完当前任务才有机再去执行另一个任务。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor中关闲线程池的方法shutdown与shutdownNow的区别是什么?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"shutdown方法是将线程池的状态设置为SHUTDOWN,此时新任务不能被提交(提交会抛出异常),workerQueue的任务会被继续执行,同时线程池会向那些空闲的线程发出中断信号。空闲的线程实际就不没在执行任务的线程。如何被封装在worker里的线程能加锁,这里这个线程实现会就空闲的。下面是向空闲的线程发出中断信号源码。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" private void interruptIdleWorkers(boolean onlyOne) {\n final ReentrantLock mainLock = this.mainLock;\n mainLock.lock();\n try {\n for (Worker w : workers) {\n Thread t = w.thread;\n //w.tryLock()用于加锁,看线程是否在执行任务\n if (!t.isInterrupted() && w.tryLock()) {\n try {\n t.interrupt();\n } catch (SecurityException ignore) {\n } finally {\n w.unlock();\n }\n }\n if (onlyOne)\n break;\n }\n } finally {\n mainLock.unlock();\n }\n }\n复制代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"shutdownNow方法是将线程池的状态设置为STOP,此时新任务不能被提交(提交会抛出异常),线程池中所有线程都会收到中断的信号。具体线程会作出什么响应,要看情况,如果线程因为调用了Object的wait、join方法或是自身的sleep方法而阻塞,那么中断状态会被清除,同时抛出InterruptedException。其它情况可以参考Thread.interrupt方法的说明。shutdownNow方法向所有线程发出中断信息源码如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"private void interruptWorkers() {\n final ReentrantLock mainLock = this.mainLock;\n //加锁操作保证中断过程中不会新woker被创建\n mainLock.lock();\n try {\n for (Worker w : workers)\n w.interruptIfStarted();\n } finally {\n mainLock.unlock();\n }\n}\n复制代码"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"通过submit方法向ThreadPoolExecutor提交任务后,当所有的任务都执行完后不调用shutdown或shutdownNow方法会有问题吗?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果没指核心线程允许超时将会有问题。核心线程允许超时是指在从wokerQueue中获取任务时,采用的阻塞的获取方式等待任务到来,还是通过设置超时的方式从同步阻塞队列中获取任务。即是通通过BlockingQueue的poll方法获取任务还是take方法获取任务。可参考之前的源码分析中的getTask方法分析。如果不调用shutdown或shutdownNow方法,核心线程由于在getTask方法调用BlockingQueue.take方法获取任务而处于一直被阻塞挂起状态。核心线程将永远处于Blocking的状态,导致内存泄漏,主线程也无法退出,除非强制kill。试着运行如下程序会发现,程序无法退出。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"public class Test {\n public static void main(String args[]) {\n ExecutorService executorService = new ThreadPoolExecutor(3, 3,10L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));\n executorService.submit(new Runnable() {\n @Override\n public void run() {\n System.out.println(\"thread name \" + Thread.currentThread().getName());\n }\n });\n }\n}\n复制代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所在使用线程池时一定要记得根本具体场景调用shutdown或shutdownNow方法关闭线程池。shutdown方法适用于提交任务都要被执行完的场景,shutdownNow方法适用于不关心提交任务是否执行完的场景。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor有没有提供扩展点,方便在任务执行前或执行后做一些事情?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"线程池提供了三个扩展点,分别是提交任务的run方法或是call方法被调用前与被调后,即beforeExecutor与afaterExecutor方法;另外一个扩展点是线程池的状态从TIDYING状态流转为TERMINATED状态时terminated方法会被调用。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"总结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本来只是想写一点点,写着写着就发现又有点长。这篇主要是介绍了ThreadPoolExecutor中个人认为比较重要点,同时也是把ThreadPoolExecutor再梳理一下发现自己之前理解有偏差的地方。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"看完三件事❤️"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"关注公众号 『 "},{"type":"text","marks":[{"type":"strong"}],"text":"java烂猪皮"},{"type":"text","text":" 』,不定期分享原创知识。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"同时可以期待后续文章ing🚀"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/34/34172ad7f3cc8e0f28bd1fc6ca2d2b68.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:叶易"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"出处:"},{"type":"link","attrs":{"href":"https://club.perfma.com/article/1917631","title":null},"content":[{"type":"text","text":"club.perfma.com/article/191…"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章