Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java线程池还能死锁?一篇文章带你搞懂线程池中的一些坑点

Java线程池还能死锁?一篇文章带你搞懂线程池中的一些坑点

作者头像
程序员牛肉
发布于 2024-12-25 07:17:09
发布于 2024-12-25 07:17:09
28600
代码可运行
举报
运行总次数:0
代码可运行

大家好,我是程序员牛肉。

最近在线程池这里又踩了一个坑:“线程池死锁”,不知道你们有没有遇到过这种情况。如果你不知道这种情况的话,那可得好好看看我这篇文章了。

我们先来回顾一下什么是死锁:死锁产生的必要条件是互斥、请求与保持、不可抢占、循环等待。所以当若干进程因竞争而无休止地相互等待他方释放已占有的资源时,系统会产生死锁。

简单的讲:如果资源A和B在同一时间只能被单个线程获取,此时线程A获取了资源A,等待线程B释放资源B,而线程B获取了资源B,等待线程A获取线程B。

[最常见的死锁其实就在我们的生活中,当你去一些地方办事的时候,你会遇见以下情况:找A的时候,A说你去找B办。找到B的时候,B说找A办。这个时候AB之间就构成死锁了。]

难道说线程池还会发生这种情况?当然会有,不然我写这篇文章干什么。

线程池引入了另一种死锁情况:父线程在占用了线程池内所有的资源后又向线程池提交了新的任务,并且要等这些任务完成后才释放资源,而这些新提交的任务根本就没机会被完成,一直被堆放在阻塞队列中。

这种情况在项目代码中,大部分都是因为父子线程都使用公共线程所造成的。

例如有一个公共线程池,最大线程数为2。此时有两个线程接收到了任务进行执行。而这个任务需要创建一个子线程来执行。

于是这两个线程又尝试使用公共线程池中的线程来执行任务。结果由于线程池中所有的两个线程都已经被占有,导致没有办法创建子线程来执行任务。

而线程池中的两个线程又因为自身任务没有被执行完毕而一直存活,导致迟迟不肯让渡线程来让子线程执行任务。因此子线程就被一直存放在了有界阻塞队列中。导致后续的所有请求都一直触发线程池的淘汰策略。

示例代码为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 创建单线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();

    pool.submit(() -> {
    try {
      // 输出日志信息,表示第一个任务开始执行
      log.info("First");

      // 向线程池提交第二个任务,并等待第二个任务执行完成
      pool.submit(() -> log.info("Second")).get();

      // 输出日志信息,表示第一个任务后续操作继续执行
      log.info("Third");
    } catch (InterruptedException | ExecutionException e) {
      // 若出现异常,记录错误日志
      log.error("Error", e);
      }
    });

在这种情况下,我们的父线程在占有了线程池的所有线程之后,仍然向线程池去提交任务并且使用get方法来获取其运行结果。

此时这个任务就会一直进入内部等待队列,等待父线程让出线程供自己执行。而父线程只有在自身任务执行完毕之后才会释放线程供子线程执行任务。在这种情况下就造成了线程池死锁

那我们要如何解决这个问题呢?

其实有一个最简单的解法:既然线程池死锁之后导致有界阻塞队列被占满,引发后续任务一直触发淘汰策略,那我们选择无界阻塞队列不就好了?

后续任务尽管不会被执行,但你就一直往阻塞队列里面加就完事了。

[在 Java 中,无界阻塞队列(Unbounded Blocking Queue)是一种特殊的数据结构,它实现了BlockingQueue接口。与有界阻塞队列不同,无界阻塞队列理论上可以存储无限数量的元素。当向无界阻塞队列中添加元素时,它不会因为队列已满而阻塞(除非遇到系统资源限制,如内存不足等极端情况)]

这种方法也就图一乐,不会真有人在实际开发中使用这个方法吧?那真正的解决方法有哪些呢?

1.使用CompletableFuture的异步回调,避免阻塞线程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ExecutorService pool = Executors.newSingleThreadExecutor();

        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
运行
AI代码解释
复制

    public static void main(String[] args) {
        ExecutorService pool = Executors.newSingleThreadExecutor();

        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 = Executors.newSingleThreadExecutor();
        pool.submit(() -> log.info("Second"));
        pool.shutdown();
    }
}

拆分线程池的方法最简单高效。但是需要注意的是:尽量不要在java代码中创建过多的线程。过多的线程也会拖慢整个项目的响应速度。

如果你对Java线程池的小坑点比较好奇的话,还可以看一看我之前写的这一篇文章:

这些问题都不知道,还敢说自己熟悉Java的线程池?

2024-10-27

今天关于java线程池的小坑点就介绍到这里了,希望通过我的文章,你可以了解这个比较罕见的线程池坑点。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员牛肉 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
面试题-关于Java线程池一篇文章就够了
线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。
程序新视界
2019/12/20
1.9K0
面试题-关于Java线程池一篇文章就够了
线程池自引发死锁
线程池自引发死锁
Java架构师必看
2021/04/13
1.1K0
java线程池
一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的。在jdk1.5之后这一情况有了很大的改观。Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。 二:线程池 线程池的作用: 线程池作用就是限制系统中执行线程的数量。      根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率
xiangzhihong
2018/01/30
1.2K0
Java线程池分享
分析一下: T1,T3是多线程本身的带来的开销,希望减少T1,T3所用的时间,从而减少T的时间。如果在程序中频繁的创建或销毁线程,这导致T1和T3在T中占有相当比例。显然这是突出了线程的弱点(T1,T3),而不是优点(并发性)。线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的
每天学Java
2020/06/01
7450
Java线程池分享
由浅入深理解Java线程池及线程池的如何使用
前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担。线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory。即便没有这样的情况,大量的线程回收也会给GC带来很大的压力。 为了避免重复的创建线程,线程池的出现可以让线程进行复用。通俗点讲,当有工作来,就会向线程池拿一个线程,当工作完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。 接下来从总体到细致的方式,来共同探讨线程池。 总体的架构 来看Exe
Janti
2018/04/10
7.8K0
由浅入深理解Java线程池及线程池的如何使用
java线程池详解
在java中,执行任务的最小单位是线程。我们知道,线程是一种稀缺的资源,它的创建于销毁是一个非常耗费资源的操作,而Java线程依赖于内核线程,其线程的创建需要进行操作系统状态的切换,为了避免多度消耗资源需要设法重用线程去执行多个任务。而线程池具备缓存和管理线程的功能,可以很好的对线程进行统一分配、监控和调优。
从大数据到人工智能
2022/09/16
6700
java线程池详解
Java线程池分析
在执行一个异步任务或并发任务时,往往是通过直接new Thread()方法来创建新的线程,这样做弊端较多,更好的解决方案是合理地利用线程池,线程池的优势很明显,如下:
九州暮云
2019/08/21
4410
Java线程池分析
一文简单理解Java线程池的问题
线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核充分利用,还能防止过分调度。 线程池架构
暴躁的程序猿
2022/03/24
1580
一文简单理解Java线程池的问题
Java多线程:还不懂线程池吗?一文带你彻底搞懂!
总体来说,线程池有如下的优势: (1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 (3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Java小朔哥
2019/08/13
6460
executorservice等待线程池执行完毕_java线程池策略
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
全栈程序员站长
2022/09/30
1.3K0
executorservice等待线程池执行完毕_java线程池策略
11.JUC线程高级-线程池&Fork/Join
线程池: 提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
用户1212940
2022/04/13
2080
11.JUC线程高级-线程池&Fork/Join
线程池
package com.shi.juc; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * 一、线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。 *
用户5927264
2019/09/17
7330
线程池
【Java】线程池梳理
线程池:本质上是一种对象池,用于管理线程资源。在任务执行前,需要从线程池中拿出线程来执行。在任务执行完成之后,需要把线程放回线程池。通过线程的这种反复利用机制,可以有效地避免直接创建线程所带来的坏处。
后端码匠
2023/02/27
2900
【Java】线程池梳理
线程池-从零到一了解并掌握线程池 | 技术创作特训营第一期
注意:这里主要是考察你实际到底用没用过。真正使用过的一定会说这些创建方式的优缺点。 !!!不建议使用Executors创建线程:
@派大星
2023/08/11
1870
JUC内置线程池
整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况。
兜兜转转
2023/03/08
1750
JUC内置线程池
Java线程池详解
  当任务数量上升到1000+,这样内存开销太大,我们希望有固定数量的线程,来执行这1000个线程,这样就避免了反复创建并销毁线程所带来的开销问题。
砖业洋__
2023/05/06
4060
Java线程池详解
Java线程池
线程池,顾名思义,这是管理一堆线程而出现的对象。与数据库的连接池一致,它的出现解决了线程的频繁创建和销毁,从而浪费大量资源的问题。
半月无霜
2023/03/03
5590
Java线程池
Java的Executor框架和线程池实现原理
Executor接口是Executor框架中最基础的部分,定义了一个用于执行Runnable的execute方法,它没有实现类只有另一个重要的子接口ExecutorService
全栈程序员站长
2022/11/17
4620
Java的Executor框架和线程池实现原理
相关推荐
面试题-关于Java线程池一篇文章就够了
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验