对于一些定时任务或者网络请求服务将会使用线程池,当应用停机时需要正确安全的关闭线程池,如果处理不当,可能造成数据丢失,业务请求结果不正确等问题。

关闭线程池我们可以选择什么都不做,JVM 关闭时自然的会清除线程池对象。当然这么做,存在很大的弊端,线程池中正在执行执行的线程以及队列中还未执行任务将会变得极不可控。所以我们需要想办法控制到这些未执行的任务以及正在执行的线程。

线程池 API 提供两个主动关闭的方法 ThreadPoolExecutor#shutdownNow 与 ThreadPoolExecutor#shutdown,这两个方法都可以用于关闭线程池,但是具体效果却不太一样。

一、线程池的状态

在说线程池关闭方法之前,我们先了解线程池状态。

线程池状态关系图如下:

从上图我们看到线程池总共存在 5 种状态,分别为:

RUNNING:线程池创建之后的初始状态,这种状态下可以执行任务。

SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行结束。

STOP: 该状态下线程池不再接受新任务,但是不会处理工作队列中的任务,并且将会中断线程。

TIDYING:该状态下所有任务都已终止,将会执行 terminated() 钩子方法。

TERMINATED:执行完 terminated() 钩子方法之后。

当我们执行 ThreadPoolExecutor#shutdown 方法将会使线程池状态从 RUNNING 转变为 SHUTDOWN。而调用 ThreadPoolExecutor#shutdownNow 之后线程池状态将会从 RUNNING 转变为 STOP。从上面的图上还可以看到,当线程池处于 SHUTDOWN,我们还是可以继续调用 ThreadPoolExecutor#shutdownNow 方法,将其状态转变为 STOP 。

二、ThreadPoolExecutor#shutdown

上面我们知道线程池状态,这里先说说 shutdown 方法。shutdown 方法源码比较简单,能比较直观理解其调用逻辑。

shutdown 方法源码:

public void shutdown() {

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

// 检查权限

checkShutdownAccess();

// 设置线程池状态

advanceRunState(SHUTDOWN);

// 中断空闲线程

interruptIdleWorkers();

// 钩子函数,主要用于清理一些资源

onShutdown();

} finally {

mainLock.unlock();

}

tryTerminate();

}

shutdown 方法首先加锁,其次先检查系统安装状态。接着就会将线程池状态变为 SHUTDOWN,在这之后线程池不再接受提交的新任务。此时如果还继续往线程池提交任务,将会使用线程池拒绝策略响应,默认情况下将会使用 ThreadPoolExecutor.AbortPolicy,抛出 RejectedExecutionException 异常。

interruptIdleWorkers 方法只会中断空闲的线程,不会中断正在执行任务的的线程。空闲的线程将会阻塞在线程池的阻塞队列上。

线程池构造参数需要指定 coreSize(核心线程池数量),maximumPoolSize(最大的线程池数量),keepAliveTime(多余空闲线程等待时间),unit(时间单位),workQueue(阻塞队列)。

当调用线程池的 execute 方法,线程池工作流程如下:

如果此时线程池中线程数量小于 coreSize,将会新建线程执行提交的任务。

如果此时线程池线程数量已经大于 coreSize,将会直接把任务加入到队列中。线程将会从工作队列中获取任务执行。

如果工作队列已满,将会继续新建线程。

如果工作队列已满,且线程数等于 maximumPoolSize,此时将会使用拒绝策略拒绝任务。

超过 coreSize 数量那部分线程,如果空闲了 keepAliveTime ,线程将会终止。

工作流程图如下:

当线程池处于第二步时,线程将会使用 workQueue#take 获取队头的任务,然后完成任务。如果工作队列一直没任务,由于队列为阻塞队列,workQueue#take 将会阻塞线程。

三、ThreadPoolExecutor#shutdownNow

ThreadPoolExecutor#shutdownNow 源码如下:

public List shutdownNow() {

List tasks;

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

// 检查状态

checkShutdownAccess();

// 将线程池状态变为 STOP

advanceRunState(STOP);

// 中断所有线程,包括工作线程以及空闲线程

interruptWorkers();

// 丢弃工作队列中存量任务

tasks = drainQueue();

} finally {

mainLock.unlock();

}

tryTerminate();

return tasks;

}

shutdownNow 方法将会把线程池状态设置为 STOP,然后中断所有线程,最后取出工作队列中所有未完成的任务返回给调用者。

对比 shutdown 方法,shutdownNow 方法比较粗暴,直接中断工作线程。不过这里需要注意,中断线程并不代表线程立刻结束。这里需要线程主动配合线程中断响应。

线程中断机制: thread#interrupt 只是设置一个中断标志,不会立即中断正常的线程。如果想让中断立即生效,必须在线程 内调用 Thread.interrupted() 判断线程的中断状态。 对于阻塞的线程,调用中断时,线程将会立刻退出阻塞状态并抛出 InterruptedException 异常。所以对于阻塞线程需要正确处理InterruptedException 异常。

awaitTermination

线程池 shutdown 与 shutdownNow 方法都不会主动等待执行任务的结束,如果需要等到线程池任务执行结束,需要调用 awaitTermination 主动等待任务调用结束。

调用方法如下:

threadPool.shutdown();

try {

while (!threadPool.awaitTermination(60,TimeUnit.SECONDS)){

System.out.println("线程池任务还未执行结束");

}

} catch (InterruptedException e) {

e.printStackTrace();

}

如果线程池任务执行结束,awaitTermination 方法将会返回 true,否则当等待时间超过指定时间后将会返回 false。

如果需要使用这种进制,建议在上面的基础上增加一定重试次数。这个真的很重要!!!

四、优雅关闭线程池

回顾上面线程池状态关系图,我们可以知道处于 SHUTDOWN 的状态下的线程池依旧可以调用 shutdownNow。所以我们可以结合 shutdown , shutdownNow,awaitTermination ,更加优雅关闭线程池。

threadPool.shutdown(); // Disable new tasks from being submitted

// 设定最大重试次数

try {

// 等待 60 s

if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {

// 调用 shutdownNow 取消正在执行的任务

threadPool.shutdownNow();

// 再次等待 60 s,如果还未结束,可以再次尝试,或则直接放弃

if (!threadPool.awaitTermination(60, TimeUnit.SECONDS))

System.err.println("线程池任务未正常执行结束");

}

} catch (InterruptedException ie) {

// 重新调用 shutdownNow

threadPool.shutdownNow();

}

对于一些定时任务或者网络请求服务将会使用线程池,当应用停机时需要正确安全的关闭线程池,如果处理不当,可能造成数据丢失,业务请求结果不正确等问题。

关闭线程池我们可以选择什么都不做,JVM 关闭时自然的会清除线程池对象。当然这么做,存在很大的弊端,线程池中正在执行执行的线程以及队列中还未执行任务将会变得极不可控。所以我们需要想办法控制到这些未执行的任务以及正在执行的线程。

线程池 API 提供两个主动关闭的方法 ThreadPoolExecutor#shutdownNow 与 ThreadPoolExecutor#shutdown,这两个方法都可以用于关闭线程池,但是具体效果却不太一样。

一、线程池的状态

在说线程池关闭方法之前,我们先了解线程池状态。

线程池状态关系图如下:

从上图我们看到线程池总共存在 5 种状态,分别为:

RUNNING:线程池创建之后的初始状态,这种状态下可以执行任务。

SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行结束。

STOP: 该状态下线程池不再接受新任务,但是不会处理工作队列中的任务,并且将会中断线程。

TIDYING:该状态下所有任务都已终止,将会执行 terminated() 钩子方法。

TERMINATED:执行完 terminated() 钩子方法之后。

当我们执行 ThreadPoolExecutor#shutdown 方法将会使线程池状态从 RUNNING 转变为 SHUTDOWN。而调用 ThreadPoolExecutor#shutdownNow 之后线程池状态将会从 RUNNING 转变为 STOP。从上面的图上还可以看到,当线程池处于 SHUTDOWN,我们还是可以继续调用 ThreadPoolExecutor#shutdownNow 方法,将其状态转变为 STOP 。

二、ThreadPoolExecutor#shutdown

上面我们知道线程池状态,这里先说说 shutdown 方法。shutdown 方法源码比较简单,能比较直观理解其调用逻辑。

shutdown 方法源码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public void shutdown() {

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

// 检查权限

checkShutdownAccess();

// 设置线程池状态

advanceRunState(SHUTDOWN);

// 中断空闲线程

interruptIdleWorkers();

// 钩子函数,主要用于清理一些资源

onShutdown();

} finally {

mainLock.unlock();

}

tryTerminate();

}

shutdown 方法首先加锁,其次先检查系统安装状态。接着就会将线程池状态变为 SHUTDOWN,在这之后线程池不再接受提交的新任务。此时如果还继续往线程池提交任务,将会使用线程池拒绝策略响应,默认情况下将会使用 ThreadPoolExecutor.AbortPolicy,抛出 RejectedExecutionException 异常。

interruptIdleWorkers 方法只会中断空闲的线程,不会中断正在执行任务的的线程。空闲的线程将会阻塞在线程池的阻塞队列上。

线程池构造参数需要指定 coreSize(核心线程池数量),maximumPoolSize(最大的线程池数量),keepAliveTime(多余空闲线程等待时间),unit(时间单位),workQueue(阻塞队列)。

当调用线程池的 execute 方法,线程池工作流程如下:

如果此时线程池中线程数量小于 coreSize,将会新建线程执行提交的任务。

如果此时线程池线程数量已经大于 coreSize,将会直接把任务加入到队列中。线程将会从工作队列中获取任务执行。

如果工作队列已满,将会继续新建线程。

如果工作队列已满,且线程数等于 maximumPoolSize,此时将会使用拒绝策略拒绝任务。

超过 coreSize 数量那部分线程,如果空闲了 keepAliveTime ,线程将会终止。

工作流程图如下:

当线程池处于第二步时,线程将会使用 workQueue#take 获取队头的任务,然后完成任务。如果工作队列一直没任务,由于队列为阻塞队列,workQueue#take 将会阻塞线程。

三、ThreadPoolExecutor#shutdownNow

ThreadPoolExecutor#shutdownNow 源码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public List shutdownNow() {

List tasks;

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

// 检查状态

checkShutdownAccess();

// 将线程池状态变为 STOP

advanceRunState(STOP);

// 中断所有线程,包括工作线程以及空闲线程

interruptWorkers();

// 丢弃工作队列中存量任务

tasks = drainQueue();

} finally {

mainLock.unlock();

}

tryTerminate();

return tasks;

}

shutdownNow 方法将会把线程池状态设置为 STOP,然后中断所有线程,最后取出工作队列中所有未完成的任务返回给调用者。

对比 shutdown 方法,shutdownNow 方法比较粗暴,直接中断工作线程。不过这里需要注意,中断线程并不代表线程立刻结束。这里需要线程主动配合线程中断响应。

线程中断机制: thread#interrupt 只是设置一个中断标志,不会立即中断正常的线程。如果想让中断立即生效,必须在线程 内调用 Thread.interrupted() 判断线程的中断状态。 对于阻塞的线程,调用中断时,线程将会立刻退出阻塞状态并抛出 InterruptedException 异常。所以对于阻塞线程需要正确处理InterruptedException 异常。

awaitTermination

线程池 shutdown 与 shutdownNow 方法都不会主动等待执行任务的结束,如果需要等到线程池任务执行结束,需要调用 awaitTermination 主动等待任务调用结束。

调用方法如下:

1

2

3

4

5

6

7

8

threadPool.shutdown();

try {

while (!threadPool.awaitTermination(60,TimeUnit.SECONDS)){

System.out.println("线程池任务还未执行结束");

}

} catch (InterruptedException e) {

e.printStackTrace();

}

如果线程池任务执行结束,awaitTermination 方法将会返回 true,否则当等待时间超过指定时间后将会返回 false。

如果需要使用这种进制,建议在上面的基础上增加一定重试次数。这个真的很重要!!!

四、优雅关闭线程池

回顾上面线程池状态关系图,我们可以知道处于 SHUTDOWN 的状态下的线程池依旧可以调用 shutdownNow。所以我们可以结合 shutdown , shutdownNow,awaitTermination ,更加优雅关闭线程池。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

threadPool.shutdown(); // Disable new tasks from being submitted

// 设定最大重试次数

try {

// 等待 60 s

if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {

// 调用 shutdownNow 取消正在执行的任务

threadPool.shutdownNow();

// 再次等待 60 s,如果还未结束,可以再次尝试,或则直接放弃

if (!threadPool.awaitTermination(60, TimeUnit.SECONDS))

System.err.println("线程池任务未正常执行结束");

}

} catch (InterruptedException ie) {

// 重新调用 shutdownNow

threadPool.shutdownNow();

}

mysql 关闭线程池_线程池安全的关闭方式相关推荐

  1. postgres 支持的线程数_线程池被打满了怎么处理呢,你是否真的了解线程池?

    0.前言 线程池,顾名思义就是线程的池子,在每次需要取线程去执行任务的时候,没必要每次都创建新线程执行,线程池就是起着维护线程的作用,当有任务的时候就取出一个线程执行,如果任务执行完成则把线程放回到池 ...

  2. threadpoolexecutor创建线程池_线程池ThreadPoolExecutor源码分析

    什么是线程池 创建线程要花费昂贵的资源和时间,如果任务来了才创建那么响应时间会变长,而且一个进程能创建的线程数量有限.为了避免这些问题,在程序启动的时候就创建若干线程来响应出来,它们被称为线程池,里面 ...

  3. java阻塞线程池_线程池解决阻塞方法

    一.序言 当我们需要使用线程的时候,我们可以新建一个线程,然后显式调用线程的start()方法,这样实现起来非常简便,但在某些场景下存在缺陷:如果需要同时执行多个任务(即并发的线程数量很多),频繁地创 ...

  4. java 动态线程池_线程池的参数动态调整

    经典面试题 这次的文章还是绕回了我写的第三篇原创文章<有的线程它死了,于是它变成一道面试题>中留下的几个问题: 哎,兜兜转转,走走停停.天道好轮回,苍天饶过谁? 在这篇文章中我主要回答上面 ...

  5. java开源线程池_线程池 - Java 并发性和多线程 - UDN开源文档

    线程池 线程池(Thread Pool)对于限制应用程序中同一时刻运行的线程数很有用.因为每启动一个新线程都会有相应的性能开销,每个线程都需要给栈分配一些内存等等. 我们可以把并发执行的任务传递给一个 ...

  6. springboot 压测 50并发 线程等待_线程池+CountDownLatch——高并发就是这么简单

    今天和大家分享的是:在开发服务端API时候,如何合理的运用线程池+CountDownLatch来保证API的高并发访问. 首先,作为Java开发的同学来说,java.util.concurrent并发 ...

  7. c mysql 关闭连接池_数据库连接池关闭的时间

    上一篇说到分析关闭连接与不关闭连接的性能,到后来我发现自己得出的结论有误.经过多次测试发现关闭连接和不关闭连接耗费的时间基本一样.进哥也说了这是有连接池的原因.其实,自己以前对Ado.net了解的不深 ...

  8. python qthread 线程退出_线程:概念和实现

    翻译:老齐 译者注:与本文相关图书推荐:<Python大学实用教程><跟老齐学Python:轻松入门> ★ 本文将分两部分刊发. " 第一部分 Python线程允许程 ...

  9. educoder 使用线程锁(lock)实现线程同步_线程间的通信(一)

    这篇文章主要从4个角度来讲多线程间的通信: 使用wait/notify实现线程间的通信 生产者/消费者模式的实现 方法join的使用 ThreadLocal类的使用 等待/通知机制的实现: (1)wa ...

最新文章

  1. 2022-2028年中国服装行业分析报告-产业规模现状与发展规划趋势
  2. tensorflow keras numpy 数据 规范化、标准化、归一化
  3. 概率模型与条件随机场
  4. Linux keepalived
  5. JMeter学习笔记--JMeter监听器
  6. 参加kaggle比赛
  7. python新特性_Python3.6正式版新特性预览
  8. c++循环读取多行文本文件
  9. ORACLE语句两表相减,Oracle中两个date相减
  10. Burpsuite中protobuf数据流的解析
  11. Windbg分析高内存占用问题
  12. 深度学习优化算法大全系列5:AdaDelta,RMSProp
  13. windows局域网传文件5种常用方法
  14. 无偿加班_我如何赚到我的第一百万美元(无偿代码)
  15. MPB:中科院微生物所蔡磊组-运用可培养组技术开展难培养真菌的分离和鉴定
  16. 给马斯克群发卫星算本账,星链计划跟5G有可比性么?
  17. 关于SSL认证的小坑 SSLPeerUnverifiedException
  18. Rplot函数图形参数设置
  19. 联想计算机不识别硬盘分区,联想笔记本进PE不识别硬盘
  20. 五四青年节。无奋斗,不青春!

热门文章

  1. 安卓案例:启动和停止服务
  2. android开发UI界面布局教学,android UI学习 -- 设置界面的布局(包括style的使用,selector的使用,Checkbox自定义样式,菜单项的样式)...
  3. linux开机自启服务命令,linux开机自启服务命令
  4. swat模型_SWAT-CUP(SUFI-2)的工作流程
  5. bzoj1024 [SCOI2009]生日快乐 结论+dfs
  6. 【英语学习】【WOTD】encroach 释义/词源/示例
  7. Games101 计算机图形学课程笔记: Lecture14 Ray Tracing 2
  8. 用python写一个文件管理程序下载_Python管理文件神器 os.walk
  9. eclipse中文乱码解决_Stata中文乱码顽疾解决方法-一行命令
  10. matlab 1到无穷_从零开始的matlab学习笔记——(6)符号计算与极限