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

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

代码

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

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));
<think>好的,我现在需要帮助用户解决线程池任务不执行的问题。首先,我得回忆一下线程池的基本原理和常见问题线程池任务不执行可能有很多原因,我需要逐一分析可能的因素,并结合用户提供的引用内容来找出答案。 首先,用户提供的引用[1]提到Spring Boot的定时任务使用单线程线程池,如果任务耗时过长,其他任务会被阻塞。这可能是一个原因,即线程池配置不当导致任务堆积,无法执行。这时候需要调整线程池参数,比如核心线程数和队列容量。 接下来引用[2]指出主线程线程池并发执行,可能导致线程提前结束,而线程池任务还没完成。这种情况下,可能需要等待线程池任务完成,比如使用isTerminated方法或者awaitTermination方法。这也提醒我要检查是否有主线程提前关闭的情况。 引用[3]提到使用FutureTask时,如果拒绝策略是DiscardPolicy或DiscardOldestPolicy,并且调用get()方法,会导致线程阻塞。这可能是因为被拒绝的任务的Future对象在调用get时无限等待,从而阻塞主线程。这时候需要检查拒绝策略和正确处理被拒绝的任务。 接下来,我需要综合这些信息,整理出可能的原因和解决方法。可能的原因包括: 1. **线程池配置不当**:比如核心线程数过少,队列容量不足,导致任务被拒绝或堆积。 2. **任务阻塞或死锁**:任务本身有同步问题导致线程无法继续执行。 3. **资源不足**:如内存不足,线程无法创建。 4. **主线程提前关闭**:比如在Web应用中,主线程结束导致线程池未执行。 5. **拒绝策略处理不当**:如DiscardPolicy导致任务被静默丢弃,而调用get()方法导致阻塞。 6. **异常未被捕获**:任务抛出异常而未处理,导致线程终止。 对于每个原因,需要给出对应的解决方法。例如,调整线程池参数、检查任务代码、确保资源足够、使用正确的拒绝策略、添加异常处理等。 另外,用户之前的问题可能涉及到具体的使用场景,比如Spring Boot的定时任务,或者Java中的线程池使用。需要结合这些场景给出具体的建议,比如在Spring Boot中配置线程池,或者使用shutdown和awaitTermination来等待任务完成。 还要注意用户提到的定时任务线程池默认是单线程的,这可能是一个常见的问题点,需要特别强调配置多线程的重要性。 最后,生成的相关问题应该围绕线程池的配置、优化、异常处理等,帮助用户进一步理解。</think>### 线程池任务不执行的可能原因及解决方法 #### 1. **线程池配置不当** - **原因**:核心线程数不足或队列容量过小,导致任务被拒绝或堆积。例如Spring Boot默认使用单线程执行定时任务,若前序任务耗时过长会阻塞后续任务[^1]。 - **解决**: - 调整线程池参数:如`corePoolSize`、`maxPoolSize`、`queueCapacity`。 - 对于Spring Boot定时任务,可通过自定义线程池配置实现多线程执行: ```java @Configuration @EnableScheduling public class SchedulerConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } } ``` #### 2. **任务被拒绝策略静默丢弃** - **原因**:当线程池满且队列已满时,若使用`DiscardPolicy`或`DiscardOldestPolicy`拒绝策略,任务会被静默丢弃。若此时调用`Future.get()`,主线程可能被无限阻塞[^3]。 - **解决**: - 改用其他拒绝策略(如`CallerRunsPolicy`,让提交任务线程执行被拒绝的任务)。 - 避免在拒绝策略为`DiscardPolicy`时调用无参`get()`方法,或设置超时时间: ```java future.get(10, TimeUnit.SECONDS); ``` #### 3. **主线程提前终止** - **原因**:主线程未等待线程池任务完成直接关闭,导致未执行的任务被中断[^2]。 - **解决**: - 使用`shutdown()`和`awaitTermination()`确保任务完成: ```java executor.shutdown(); executor.awaitTermination(60, TimeUnit.SECONDS); ``` - 或通过`isTerminated()`轮询检查线程池状态[^2]。 #### 4. **任务存在阻塞或死锁** - **原因**:任务代码中存在同步问题(如死锁、无限循环),导致线程无法释放。 - **解决**: - 使用`jstack`或IDE调试工具排查线程状态。 - 优化任务代码逻辑,避免长时间阻塞。 #### 5. **线程池未正确初始化** - **原因**:线程池未被启动(如未调用`submit()`/`execute()`方法)或已被关闭。 - **解决**: - 检查线程池初始化代码,确保任务正确提交。 - 避免重复调用`shutdown()`导致线程池提前终止。 #### 6. **未捕获的任务异常** - **原因**:任务抛出未捕获的异常,导致执行线程终止。 - **解决**: - 在任务代码中添加`try-catch`块。 - 通过`ThreadFactory`为线程设置全局异常处理器: ```java ThreadFactory factory = r -> { Thread t = new Thread(r); t.setUncaughtExceptionHandler((thread, ex) -> { System.err.println("Task failed: " + ex.getMessage()); }); return t; }; ExecutorService executor = Executors.newFixedThreadPool(2, factory); ``` --- ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值