Java线程池还能死锁?一篇文章带你搞懂线程池中的一些坑点
Java线程池还能死锁?一篇文章带你搞懂线程池中的一些坑点
大家好,我是程序员牛肉。
最近在线程池这里又踩了一个坑:“线程池死锁”,不知道你们有没有遇到过这种情况。如果你不知道这种情况的话,那可得好好看看我这篇文章了。
我们先来回顾一下什么是死锁:死锁产生的必要条件是互斥、请求与保持、不可抢占、循环等待。所以当若干进程因竞争而无休止地相互等待他方释放已占有的资源时,系统会产生死锁。
简单的讲:如果资源A和B在同一时间只能被单个线程获取,此时线程A获取了资源A,等待线程B释放资源B,而线程B获取了资源B,等待线程A获取线程B。
[最常见的死锁其实就在我们的生活中,当你去一些地方办事的时候,你会遇见以下情况:A的时候,A说你去B办。到B的时候,B说A办。这个时候AB之间就构成死锁了。]
难道说线程池还会发生这种情况?当然会有,不然我写这篇文章干什么。
线程池引入了另一种死锁情况:父线程在占用了线程池内所有的资源后又向线程池提交了新的任务,并且要等这些任务完成后才释放资源,而这些新提交的任务根本就没机会被完成,一直被堆放在阻塞队列中。
这种情况在项目代码中,大部分都是因为父子线程都使用公共线程所造成的。
例如有一个公共线程池,最大线程数为2。此时有两个线程接收到了任务进行执行。而这个任务需要创建一个子线程来执行。
于是这两个线程又尝试使用公共线程池中的线程来执行任务。结果由于线程池中所有的两个线程都已经被占有,导致没有办法创建子线程来执行任务。
而线程池中的两个线程又因为自身任务没有被执行完毕而一直存活,导致迟迟不肯让渡线程来让子线程执行任务。因此子线程就被一直存放在了有界阻塞队列中。导致后续的所有请求都一直触发线程池的淘汰策略。
示例代码为:
代码语言:javascript代码运行次数:0运行复制// 创建单线程的线程池
ExecutorService pool = ();
pool.submit(() -> {
try {
// 输出日志信息,表示第一个任务开始执行
log.info("First");
// 向线程池提交第二个任务,并等待第二个任务执行完成
pool.submit(() -> log.info("Second")).get();
// 输出日志信息,表示第一个任务后续操作继续执行
log.info("Third");
} catch (InterruptedException | ExecutionException e) {
// 若出现异常,记录错误日志
("Error", e);
}
});
在这种情况下,我们的父线程在占有了线程池的所有线程之后,仍然向线程池去提交任务并且使用get方法来获取其运行结果。
此时这个任务就会一直进入内部等待队列,等待父线程让出线程供自己执行。而父线程只有在自身任务执行完毕之后才会释放线程供子线程执行任务。在这种情况下就造成了线程池死锁
那我们要如何解决这个问题呢?
其实有一个最简单的解法:既然线程池死锁之后导致有界阻塞队列被占满,引发后续任务一直触发淘汰策略,那我们选择阻塞队列不就好了?
后续任务尽管不会被执行,但你就一直往阻塞队列里面加就完事了。
[在 Java 中,阻塞队列(Unbounded Blocking Queue)是一种特殊的数据结构,它实现了BlockingQueue
接口。与有界阻塞队列不同,阻塞队列理论上可以存储无限数量的元素。当向阻塞队列中添加元素时,它不会因为队列已满而阻塞(除非遇到系统资源限制,如内存不足等极端情况)]
这种方法也就图一乐,不会真有人在实际开发中使用这个方法吧?那真正的解决方法有哪些呢?
1.使用CompletableFuture的异步回调,避免阻塞线程:
代码语言:javascript代码运行次数:0运行复制ExecutorService pool = ();
pool.submit(() -> {
try {
log.info("First");
// 使用CompletableFuture异步提交子任务,并在子任务完成后执行后续逻辑
CompletableFuture.runAsync(() -> log.info("Second"), pool)
.thenRun(() -> log.info("Third"));
} catch (Exception e) {
log.log(Level.SEVERE, "Error", e);
}
});
[CompletableFuture.runAsync是 Java 8 引入的CompletableFuture类中的一个静态方法。它用于以异步的方式执行一个Runnable任务,即这个任务会在一个独立于当前线程的线程中执行。这种异步执行机制可以提高程序的并发性能,避免当前线程因为等待任务完成而被阻塞,使程序能够同时处理多个任务。]
使用了这个方法后,父线程就不会阻塞等待子线程调用的结果,这样就可以在执行完父线程后,让渡线程给子线程来执行任务。
2.拆分线程池,禁止父子线程共享一个线程池:
代码语言:javascript代码运行次数:0运行复制
public static void main(String[] args) {
ExecutorService pool = ();
pool.submit(ThreadPoolExampleRewrittenWithMethods::firstTask);
pool.shutdown();
}
public static void firstTask() {
try {
log.info("First");
secondTask();
log.info("Third");
} catch (Exception e) {
log.log(Level.SEVERE, "Error", e);
}
}
public static void secondTask() {
ExecutorService pool = ();
pool.submit(() -> log.info("Second"));
pool.shutdown();
}
}
拆分线程池的方法最简单高效。但是需要注意的是:尽量不要在java代码中创建过多的线程。过多的线程也会拖慢整个项目的响应速度。
如果你对Java线程池的小坑点比较好奇的话,还可以看一看我之前写的这一篇文章:
这些问题都不知道,还敢说自己熟悉Java的线程池?
2024-10-27
今天关于java线程池的小坑点就介绍到这里了,希望通过我的文章,你可以了解这个比较罕见的线程池坑点。
本文参与 腾讯云自媒体同步曝光计划,分享自。原始发表:2024-12-25,如有侵权请联系 cloudcommunity@tencent 删除队列线程线程池异步java#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格
上一篇:CAN总线错误处理机制
下一篇:微众银行数据库架构编年史
推荐阅读
留言与评论(共有 20 条评论) |
本站网友 睿智是什么意思 | 12分钟前 发表 |
最近在线程池这里又踩了一个坑:“线程池死锁” | |
本站网友 不孕不育专科 | 12分钟前 发表 |
当向阻塞队列中添加元素时 | |
本站网友 付小芳 | 19分钟前 发表 |
即这个任务会在一个独立于当前线程的线程中执行 | |
本站网友 运营官 | 28分钟前 发表 |
阻塞队列理论上可以存储无限数量的元素 | |
本站网友 洋槐蜂蜜的功效 | 18分钟前 发表 |
并且要等这些任务完成后才释放资源 | |
本站网友 江玉郎 | 19分钟前 发表 |
当向阻塞队列中添加元素时 | |
本站网友 天津电影 | 23分钟前 发表 |
不可抢占 | |
本站网友 王仁元 | 21分钟前 发表 |
使程序能够同时处理多个任务 | |
本站网友 华夏心理咨询师 | 3分钟前 发表 |
2.拆分线程池 | |
本站网友 女医生检查前列腺 | 20分钟前 发表 |
结果由于线程池中所有的两个线程都已经被占有 | |
本站网友 中药美白祛斑面膜配方 | 1分钟前 发表 |
如果你对Java线程池的小坑点比较好奇的话 | |
本站网友 日渐式微 | 18分钟前 发表 |
在这种情况下就造成了线程池死锁那我们要如何解决这个问题呢?其实有一个最简单的解法:既然线程池死锁之后导致有界阻塞队列被占满 | |
本站网友 大兴电影院 | 2分钟前 发表 |
B说A办 | |
本站网友 高档公寓 | 23分钟前 发表 |
导致迟迟不肯让渡线程来让子线程执行任务 | |
本站网友 百度图像识别 | 0秒前 发表 |
而线程池中的两个线程又因为自身任务没有被执行完毕而一直存活 | |
本站网友 早恋接吻 | 5分钟前 发表 |
它不会因为队列已满而阻塞(除非遇到系统资源限制 | |
本站网友 哈尔滨金域蓝城 | 0秒前 发表 |
它用于以异步的方式执行一个Runnable任务 | |
本站网友 美白效果好 | 11分钟前 发表 |
e); } } public static void secondTask() { ExecutorService pool = (); pool.submit(() -> log.info("Second")); pool.shutdown(); } } 拆分线程池的方法最简单高效 | |
本站网友 偶巴尔坛 | 27分钟前 发表 |
]使用了这个方法后 |