Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理
相关文章目录:
Java线程池ThreadPoolExecutor使用和分析(一)
Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理
Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理
终止线程池主要有两个方法:shutdown() 和 shutdownNow()。
shutdown()后线程池将变成shutdown状态,此时不接收新任务,但会处理完正在运行的 和 在阻塞队列中等待处理的任务。
shutdownNow()后线程池将变成stop状态,此时不接收新任务,不再处理在阻塞队列中等待的任务,还会尝试中断正在处理中的工作线程。
下面是对线程池的几种终止方式的分析,基于JDK 1.7
以下是本文的目录大纲:
一、shutdown() -- 温柔的终止线程池
interruptIdleWorkers() -- 中断空闲worker
tryTerminate() -- 尝试终止线程池
二、shutdownNow() -- 强硬的终止线程池
interruptWorkers() -- 中断所有worker
三、awaitTermination() -- 等待线程池终止
若有不正之处请多多谅解,欢迎批评指正、互相讨论。
请尊重作者劳动成果,转载请标明原文链接:
http://www.cnblogs.com/trust-freedom/p/6693601.html
一、shutdown() -- 温柔的终止线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
shutdown()执行流程:
1、上锁,mainLock是线程池的主锁,是可重入锁,当要操作workers set这个保持线程的HashSet时,需要先获取mainLock,还有当要处理largestPoolSize、completedTaskCount这类统计数据时需要先获取mainLock
2、判断调用者是否有权限shutdown线程池
3、使用CAS操作将线程池状态设置为shutdown,shutdown之后将不再接收新任务
4、中断所有空闲线程 interruptIdleWorkers()
5、onShutdown(),ScheduledThreadPoolExecutor中实现了这个方法,可以在shutdown()时做一些处理
6、解锁
7、尝试终止线程池 tryTerminate()
可以看到shutdown()方法最重要的几个步骤是:更新线程池状态为shutdown、中断所有空闲线程、tryTerminated()尝试终止线程池
那么,什么是空闲线程?interruptIdleWorkers() 是怎么中断空闲线程的?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
interruptIdleWorkers() 首先会获取mainLock锁,因为要迭代workers set,在中断每个worker前,需要做两个判断:
1、线程是否已经被中断,是就什么都不做
2、worker.tryLock() 是否成功
第二个判断比较重要,因为Worker类除了实现了可执行的Runnable,也继承了AQS,本身也是一把锁,具体可见 ThreadPoolExecutor内部类Worker解析
tryLock()调用了Worker自身实现的tryAcquire()方法,这也是AQS规定子类需要实现的尝试获取锁的方法
1 2 3 4 5 6 7 |
|
tryAcquire()先尝试将AQS的state从0-->1,返回true代表上锁成功,并设置当前线程为锁的拥有者
可以看到compareAndSetState(0, 1)只尝试了一次获取锁,且不是每次state+1,而是0-->1,说明锁不是可重入的
但是为什么要worker.tryLock()获取worker的锁呢?
这就是Woker类存在的价值之一,控制线程中断
在runWorker()方法中每次获取到task,task.run()之前都需要worker.lock()上锁,运行结束后解锁,即正在运行任务的工作线程都是上了worker锁的
在interruptIdleWorkers()中断之前需要先tryLock()获取worker锁,意味着正在运行的worker不能中断,因为worker.tryLock()失败,且锁是不可重入的
故shutdown()只有对能获取到worker锁的空闲线程(正在从workQueue中getTask(),此时worker没有加锁)发送中断信号
由此可以将worker划分为: |
正阻塞在getTask()获取任务的worker在被中断后,会抛出InterruptedException,不再阻塞获取任务
捕获中断异常后,将继续循环到getTask()最开始的判断线程池状态的逻辑,当线程池是shutdown状态,且workQueue.isEmpty时,return null,进行worker线程退出逻辑
某些情况下,interruptIdleWorkers()时多个worker正在运行,不会对其发出中断信号,假设此时workQueue也不为空
那么当多个worker运行结束后,会到workQueue阻塞获取任务,获取到的执行任务,没获取到的,如果还是核心线程,会一直workQueue.take()阻塞住,线程无法终止,因为workQueue已经空了,且shutdown后不会接收新任务了
这就需要在shutdown()后,还可以发出中断信号
Doug Lea大神巧妙的在所有可能导致线程池产终止的地方安插了tryTerminated()尝试线程池终止的逻辑,并在其中判断如果线程池已经进入终止流程,没有任务等待执行了,但线程池还有线程,中断唤醒一个空闲线程
shutdown()的最后也调用了tryTerminated()方法,下面看看这个方法的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
|
tryTerminate() 执行流程:
1、判断线程池是否需要进入终止流程(只有当shutdown状态+workQueue.isEmpty 或 stop状态,才需要)
2、判断线程池中是否还有线程,有则 interruptIdleWorkers(ONLY_ONE) 尝试中断一个空闲线程(正是这个逻辑可以再次发出中断信号,中断阻塞在获取任务的线程)
3、如果状态是SHUTDOWN,workQueue也为空了,正在运行的worker也没有了,开始terminated
会先上锁,将线程池置为tidying状态,之后调用需子类实现的 terminated(),最后线程池置为terminated状态,并唤醒所有等待线程池终止这个Condition的线程
二、shutdownNow() -- 强硬的终止线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
shutdownNow() 和 shutdown()的大体流程相似,差别是:
1、将线程池更新为stop状态
2、调用 interruptWorkers() 中断所有线程,包括正在运行的线程
3、将workQueue中待处理的任务移到一个List中,并在方法最后返回,说明shutdownNow()后不会再处理workQueue中的任务
interruptWorkers()
1 2 3 4 5 6 7 8 9 10 |
|
interruptWorkers() 很简单,循环对所有worker调用 interruptIfStarted(),其中会判断worker的AQS state是否大于0,即worker是否已经开始运作,再调用Thread.interrupt()
需要注意的是,对于运行中的线程调用Thread.interrupt()并不能保证线程被终止,task.run()内部可能捕获了InterruptException,没有上抛,导致线程一直无法结束
三、awaitTermination() -- 等待线程池终止
参数:
timeout:超时时间
unit: timeout超时时间的单位
返回:
true:线程池终止
false:超过timeout指定时间
在发出一个shutdown请求后,在以下3种情况发生之前,awaitTermination()都会被阻塞
1、所有任务完成执行
2、到达超时时间
3、当前线程被中断
1 2 3 4 |
|
awaitTermination() 循环的判断线程池是否terminated终止 或 是否已经超过超时时间,然后通过termination这个Condition阻塞等待一段时间
termination.awaitNanos() 是通过 LockSupport.parkNanos(this, nanosTimeout)实现的阻塞等待
阻塞等待过程中发生以下具体情况会解除阻塞(对上面3种情况的解释):
1、如果发生了 termination.signalAll()(内部实现是 LockSupport.unpark())会唤醒阻塞等待,且由于ThreadPoolExecutor只有在 tryTerminated()尝试终止线程池成功,将线程池更新为terminated状态后才会signalAll(),故awaitTermination()再次判断状态会return true退出
2、如果达到了超时时间 termination.awaitNanos() 也会返回,此时nano==0,再次循环判断return false,等待线程池终止失败
3、如果当前线程被 Thread.interrupt(),termination.awaitNanos()会上抛InterruptException,awaitTermination()继续上抛给调用线程,会以异常的形式解除阻塞
故终止线程池并需要知道其是否终止可以用如下方式:
1 2 3 4 5 6 7 8 9 |
|
参考资料:
深入理解java线程池—ThreadPoolExecutor
原文链接:https://www.cnblogs.com/trust-freedom/p/6693601.html
作者:Trust_FreeDom - 博客园
博客主页:http://www.cnblogs.com/trust-freedom/
欢迎转载,但请保留作者和本文链接,谢谢!
欢迎在下面的评论区与我交流。
如果觉得写的不错,请点击下面的“推荐”按钮,让我更有动力写出更好的文章。
分类: Java并发编程
Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理相关推荐
- Java线程池ThreadPoolExecutor使用和分析
Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) Java线程池ThreadPoolExecutor使用和分析(三 ...
- Java并发编程实战(chapter_3)(线程池ThreadPoolExecutor源码分析)
为什么80%的码农都做不了架构师?>>> 这个系列一直没再写,很多原因,中间经历了换工作,熟悉项目,熟悉新团队等等一系列的事情.并发课题对于Java来说是一个又重要又难的一大块 ...
- JAVA线程池(ThreadPoolExecutor)源码分析
JAVA5提供了多种类型的线程池,如果你对这些线程池的特点以及类型不太熟悉或者非常熟悉,请帮忙看看这篇文章(顺便帮忙解决里面存在的问题,谢谢!): http://xtu-xiaoxin.ite ...
- Java线程池ThreadPoolExecutor源码分析
继承关系 Executor接口 public interface Executor {void execute(Runnable command); } ExecutorService接口 publi ...
- Java 进阶——多线程优化之线程池 ThreadPoolExecutor的使用(三)
引言 前面花了很多时间把线程池的核心容器和主要核心流程源码大概的分析了一遍,如果有认真看了的话相信,一定对于线程池有了较深的理解,ThreadPoolExecutor是线程池框架的一个核心类,通过对T ...
- c++ 线程池_JAVA并发编程:线程池ThreadPoolExecutor源码分析
前面的文章已经详细分析了线程池的工作原理及其基本应用,接下来本文将从底层源码分析一下线程池的执行过程.在看源码的时候,首先带着以下两个问题去仔细阅读.一是线程池如何保证核心线程数不会被销毁,空闲线程数 ...
- threadpoolexecutor创建线程池_线程池ThreadPoolExecutor源码分析
什么是线程池 创建线程要花费昂贵的资源和时间,如果任务来了才创建那么响应时间会变长,而且一个进程能创建的线程数量有限.为了避免这些问题,在程序启动的时候就创建若干线程来响应出来,它们被称为线程池,里面 ...
- 【java】java中的线程池 ThreadPoolExecutor源码分析
文章目录 1.概述 4.源码 4.1 关键属性 4.2 构造函数 4.4 状态控制 4.5 ThreadLocalMap 4.6 execute方法源码分析 4.7 addWorker方法源码分析 4 ...
- java多线程交替打印_使用Java实现三个线程交替打印0-74
使用Java实现三个线程交替打印0-74 题目分析 三个线程交替打印,即3个线程是按顺序执行的.一个线程执行完之后,唤醒下一个线程,然后阻塞,等待被该线程的上一个线程唤醒.执行的顺序是一个环装的队列 ...
最新文章
- 信息瓶颈提出者Naftali Tishby生前指导,129页博士论文「神经网络中的信息流」公布...
- Java压缩html
- python 抢购口罩_Python 京东口罩监控+抢购
- BestCoder Round #86 1003 HDU 5806——NanoApe Loves Sequence Ⅱ
- 水系图一般在哪里找得到_进展 | 水系钠离子电池研究取得重要进展
- 采用python语言实现猜数游戏_用python实现猜数游戏
- 【caffe-windows】 caffe-master 之 mnist 超详细
- 一款支持vue3 的颜色选择器
- Android设置RecyclerView的Header和Footer
- 日知录(七):python之理解pygame飞机大战
- Costco市值10年增长5倍的秘诀:水坝式经营
- 令牌桶生成令牌_使用令牌的经典ASP登录系统
- 我在亚马逊云平台的学习成长之路
- 飞腾arm服务器下的银河麒麟V10 yum 安装docker
- 数学建模之MATLAB编程
- 三星手机html默认,关于三星手机恢复出厂设置的方法
- C#与.NET程序员面试宝典 1.2.1 求职信的写法、原则及技巧
- linux mdadm 脚本,Linux下用mdadm实现软件RAID
- 攻防世界simple php,攻防世界 新手训练营 simple_php
- 【Javascript】【注意事项】script标签不能自闭合