三.多线程JUC篇-3.15 ThreadPoolExecutor

柔光的暖阳◎ 2022-12-21 11:00 213阅读 0赞

1.构造参数详细讲解

  • corePoolSize:常驻核心线程数,也就是说这是线程池中始终存活的线程,即使是空闲状态,也不会被销毁,除非关闭线程池。但有个特殊,设置了allowCoreThreadTimeout
  • maximumPoolSize:最大线程数,线程池中最多能存活的线程数
  • keepAliveTime:空闲线程存活时间,配合下一个参数unit,当线程池中的线程数量超过corePoolSize,会清空多余空闲线程,以便使线程池数量保持到corePoolSize数量
  • unit: 空闲线程存活时间的单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS等
  • workQueue: 工作队列(阻塞队列、等待队列),调用submit方法,任务首先进入此队列中,而不是立即执行,队列的容量决定了能有多少请求被阻塞队列接受
  • threadFactory: 线程工厂,一般使用默认Executors.defaultThreadFactory()即可
  • handler:线程池的拒绝策略。当等待队列已经满了,再也塞不下新的任务;同时,线程池也满了(即达到maximumPoolSize及workQueue满),无法为新任务服务。这时,我们需要拒绝策略机制合理的处理这个问题

2. 任务什么时候被执行

有时候,您会发现提交的任务,并没有被立即执行,只执行了corePoolSize个任务。这个可能会给我们带来困惑,还没有达到maximumPoolSize,怎么还有部分任务不执行呢?其实是新任务进入了workQueue,workQueue没有满。有二种情况下,剩余部分任务会被执行,第一种workQueue满了,第二种线程池中有线程执行完任务后。

  • 初次分配时,线程池会分配corePoolSize数量的线程,用于执行当前提交的任务
  • 任务提交超过corePoolSize数量的部分,会暂时放至workQueue;待线程池中有空闲线程时,会从workQueue取任务
  • 提交的任务过多,以至于workQueue也放下去了,线程池会在分配线程直到maximumPoolSize;如果线程池中的某个线程执行完了,又没有达到keepAliveTime时,会从workQueue取任务
  • 提交的任务更多了,以至于超过了maximumPoolSize和workQueue大小,会执行handler策略

    1. public class ThreadPoolExecutorBuild {
    2. public static void main(String[] args) {
    3. ThreadPoolExecutor executorService = (ThreadPoolExecutor)buildThreadPoolExecutor();
    4. int activeCount = -1;
    5. int queueSize = -1;
    6. while(true){
    7. if (activeCount != executorService.getActiveCount() || queueSize != executorService.getQueue().size()) {
    8. System.out.println(executorService.getActiveCount());
    9. System.out.println(executorService.getCorePoolSize());
    10. System.out.println(executorService.getMaximumPoolSize());
    11. System.out.println(executorService.getQueue().size());
    12. activeCount = executorService.getActiveCount();
    13. queueSize = executorService.getQueue().size();
    14. System.out.println("==================");
    15. }
    16. }
    17. }
    18. private static ExecutorService buildThreadPoolExecutor(){
    19. ExecutorService executorService = new ThreadPoolExecutor(1, 2,
    20. 30, TimeUnit.SECONDS,
    21. new ArrayBlockingQueue<>(1),
    22. r->{ Thread t = new Thread(r);return t;},
    23. new ThreadPoolExecutor.AbortPolicy());
    24. //ToDo
    25. return executorService;
    26. }
    27. private static void sleepSeconds(long seconds){
    28. System.out.println("* " + Thread.currentThread().getName() + " *");
    29. try {
    30. TimeUnit.SECONDS.sleep(seconds);
    31. } catch (InterruptedException e) {
    32. e.printStackTrace();
    33. }
    34. }
    35. }
    36. (1)executorService.execute(()->sleepSeconds(10));
    37. //执行一个任务,线程池为当前任务分配了一个线程,可以发现活跃线程数1,10秒后任务执行完后,
    38. //活跃线程数0
    39. * Thread-0 *
    40. =====14:55:04=====
    41. 1
    42. 1
    43. 2
    44. 0
    45. =====14:55:14=====
    46. 0
    47. 1
    48. 2
    49. 0
    50. (2)executorService.execute(()->sleepSeconds(5));
    51. executorService.execute(()->sleepSeconds(10));
    52. //执行二个任务,可以发现依然活跃线程数1,但工作队列中有一个任务,5秒后第一个任务执行完,
    53. //第二个任务从工作队列中取出并执行,使用了原线程(还是Thread-0,因为corePoolSize保证始
    54. //终有一个)执行任务
    55. * Thread-0 *
    56. =====14:56:28=====
    57. 1
    58. 1
    59. 2
    60. 1
    61. * Thread-0 *
    62. =====14:56:33=====
    63. 1
    64. 1
    65. 2
    66. 0
    67. =====14:56:43=====
    68. 0
    69. 1
    70. 2
    71. 0
  1. (3)executorService.execute(()->sleepSeconds(10));
  2. executorService.execute(()->sleepSeconds(5));
  3. executorService.execute(()->sleepSeconds(8));
  4. //执行三个任务,我们会发现此时线程池分配了二个线程为三个任务(因为工作队列满了,所以
  5. //分配的线程数超过了corePoolSize),待第三个任务执行完,第二个任务开始执行了
  6. * Thread-0 *
  7. * Thread-1 *
  8. =====15:00:22=====
  9. 2
  10. 1
  11. 2
  12. 1
  13. * Thread-1 *
  14. =====15:00:30=====
  15. 2
  16. 1
  17. 2
  18. 0
  19. =====15:00:32=====
  20. 1
  21. 1
  22. 2
  23. 0
  24. =====15:00:35=====
  25. 0
  26. 1
  27. 2
  28. 0

3.线程池的关闭

  • submit()、execute():非阻塞方法,会立即返回,因为任务首次会放置在workQueue,然后线程池在从workQueue取任务执行
  • shutdown():非阻塞方法,会立即返回
  • shutdownNow():非阻塞方法,会立即返回
  • awaitTermination():阻塞方法,接收timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。

3.1 shutdown和shutdownNow区别

(1) shutdown

  • 开始一个有序的关闭,在关闭中,之前提交的任务会被执行(包含正在执行的,在阻塞队列中的)
  • 新任务会被拒绝
  • 中断空闲线程
  • 当前方法不会等待之前提交的任务执行结束,可以使用awaitTermination()

(2) shutdownNow

  • 返回正在等待被执行的任务列表
  • 新任务会被拒绝
  • 中断所有线程(shutdown只是中断空闲线程)

(3) isShutdown
只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true

(4) isTerminated
当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true

(5) 中断
如果线程中没有sleep 、wait、Condition、定时锁等可中断点,interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出

4.监控线程池

(1) 如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。

(2)可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性:

  • taskCount:线程池需要执行的任务数量,是个近似值。
  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount,是个近似值。
  • largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
  • poolSize:线程池当前的线程数总量,包括活动的线程与闲置的线程。
  • activeCount:获取活动的线程数。

发表评论

表情:
评论列表 (有 0 条评论,213人围观)

还没有评论,来说两句吧...

相关阅读