线程池newFixedThreadPool父子任务导致的线程死锁问题

本文分析了一段Java代码中,使用固定线程池引发的死锁问题。通过实例和线程状态分析,揭示了线程池设计不当导致的任务执行阻塞。解决建议转向自定义线程池配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码

先来看这么一段代码
判断一下是否有问题?

public class ThreadPoolLock {

    /**
     * 固定大小为2的线程池
     */
    private static ExecutorService pool = Executors.newFixedThreadPool(2);

    /**
     * 测试
     * 开了十个父任务
     */
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            pool.submit(new PTask(pool));
        }
    }


}

/**
 * 父任务
 */
class PTask implements Runnable {

    private int subNum = 3;
    private ExecutorService pool;

    public PTask(ExecutorService pool) {
        this.pool = pool;
    }

    /**
     * 一个父任务下要执行完subNum=3个子任务
     * 这个父任务才算执行完成
     * 否则会一直await
     */
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"父任务");
        CountDownLatch latch = new CountDownLatch(subNum);
        for (int i = 0; i < subNum; i++) {
            pool.execute(new STask(i,latch));
        }

        // 父任务阻塞 等待子任务执行完成
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 子任务
 */
class STask implements Runnable {
    private int num;
    private CountDownLatch latch;

    public STask(int num, CountDownLatch latch) {
        this.num = num;
        this.latch = latch;
    }

    /**
     * 执行完成一个子任务
     * countDown
     */
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"子任务"+num);
        latch.countDown();
    }
}

解析

运行结果

在这里插入图片描述
就打印了两个父任务的执行,就没动静了。

我们看一下线程的状态

  1. 开发环境下,idea可以点击“相机图标”查看线程信息
    在这里插入图片描述
  2. 要是在服务器上,我们还是老老实实的用命令查看线程信息
14329 Jps
14317 Launcher
14318 ThreadPoolLock
apple$ jstack 14318 > ./thread.txt
apple$ cat thread.txt

2021-07-04 22:43:22
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):

"Attach Listener" #13 daemon prio=9 os_prio=31 tid=0x00007fb5748d4000 nid=0x3e03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #12 prio=5 os_prio=31 tid=0x00007fb5740ae000 nid=0x1903 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"pool-1-thread-2" #11 prio=5 os_prio=31 tid=0x00007fb574099800 nid=0x3b03 waiting on condition [0x0000700003168000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x000000079596a9c8> (a java.util.concurrent.CountDownLatch$Sync)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
	at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
	at cn.itcast.pool.PTask.run(ThreadPoolLock.java:49)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:748)

"pool-1-thread-1" #10 prio=5 os_prio=31 tid=0x00007fb5748d3000 nid=0x4103 waiting on condition [0x0000700003065000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000007958c5268> (a java.util.concurrent.CountDownLatch$Sync)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
	at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
	at cn.itcast.pool.PTask.run(ThreadPoolLock.java:49)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:748)

...省略


根据以上信息,我们发现线程都处于waitting状态,
可以定位到再at cn.itcast.pool.PTask.run(ThreadPoolLock.java:49) 49行
在回过头来看代码
发现阻塞在了这里
在这里插入图片描述

CountDownLatch.await() 会阻塞,等待(子)线程执行完成,并且countDown()到0后,才会继续执行

那么问题来了?为啥阻塞在这里?因为子任务没有执行。为啥没有执行?因为线程池里没有线程。

原因分析

newFixedThreadPool:固定线程数量的线程池,核心线程 = 最大线程,是无边队列

代码里 我们用了
ExecutorService pool = Executors.newFixedThreadPool(2);

开了两个线程去跑任务,输出的结果为

pool-1-thread-1父任务
pool-1-thread-2父任务

也就是说,这两个线程执行了父任务。
继续回过头查看代码
打印完成,向线程池提交子任务完成,阻塞完成。

然后,没有线程了。2个线程都跑了父任务。
子任务没有线程执行。

于是…
父任务等待着子任务执行完成好释放锁。
子任务又等着父任务执行完成好释放锁。

死锁产生了。

在这里插入图片描述

总结

主要原因还是父子线程共用了一个线程池,线程的分配不合理造成。
所以最好还是自定义线程吧(参考)。

    private static ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,
            1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值