You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何含Thread.Sleep()的Java 8代码未触发死锁?

问题分析:CompletableFuture模拟死锁未阻塞反而终止的原因

代码示例

public class MustDeadLock {
    private static final Object obj1 = new Object();
    private static final Object obj2 = new Object();

    public static void main(String[] args) {
        mockDeadLock();
    }

    public static void mockDeadLock() {

        CompletableFuture cf1 = CompletableFuture.runAsync(() -> {
            synchronized (obj1) {
                System.out.println("thread A got lock: obj1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("thread A ready to get the rest lock");
                synchronized (obj2) {
                    System.out.println("thread A got all locks");
                }
            }
        });

        CompletableFuture cf2 = CompletableFuture.runAsync(() -> {
            synchronized (obj2) {
                System.out.println("thread B got lock: obj2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("thread B ready to get the rest lock");
                synchronized (obj1) {
                    System.out.println("thread B got all locks");
                }
            }
        });

        CompletableFuture.allOf(cf1, cf2);

        System.out.println("program ready to terminate");
    }
}

疑问点

程序执行后仅输出:

thread B got lock: obj2
thread A got lock: obj1
program ready to terminate

以下两行代码未执行,程序直接终止而非进入死锁阻塞状态:

System.out.println("thread A ready to get the rest lock");
System.out.println("thread B ready to get the rest lock");

原因解析

  1. CompletableFuture默认线程池的线程是守护线程
    CompletableFuture.runAsync()默认使用ForkJoinPool.commonPool(),这个线程池中的线程属于守护线程。JVM的退出规则是:当所有非守护线程都执行完毕后,JVM会直接终止,不管守护线程是否还在运行。

  2. CompletableFuture.allOf()不会主动等待任务完成
    allOf()方法只是返回一个新的CompletableFuture,表示所有传入的任务都完成,但它不会阻塞当前线程去等待任务执行结束。主线程执行完CompletableFuture.allOf(cf1, cf2);后,直接打印program ready to terminate,随后mockDeadLock()方法执行完毕,主线程(非守护线程)结束。此时JVM检测到没有非守护线程在运行,直接终止进程,导致两个异步任务还没执行到Thread.sleep(1000)之后的打印语句就被强制停止,自然也不会进入死锁阻塞状态。

修正方案

要让程序进入预期的死锁阻塞状态,需要让主线程等待异步任务完成,或者改用非守护线程的线程池:

方案1:使用join()等待任务完成

修改mockDeadLock()方法中的allOf调用部分:

CompletableFuture.allOf(cf1, cf2).join(); // 阻塞主线程,等待所有任务完成

方案2:自定义非守护线程的线程池

创建自己的线程池,指定线程为非守护线程,然后传给runAsync():

ExecutorService executor = Executors.newFixedThreadPool(2, r -> {
    Thread t = new Thread(r);
    t.setDaemon(false); // 设置为非守护线程
    return t;
});

CompletableFuture cf1 = CompletableFuture.runAsync(() -> {
    // 原任务逻辑
}, executor);

CompletableFuture cf2 = CompletableFuture.runAsync(() -> {
    // 原任务逻辑
}, executor);

// 若要让程序保持阻塞,暂时不要关闭线程池
// executor.shutdown();

这样修改后,主线程会等待异步任务执行,两个线程会完成Thread.sleep(1000),执行到打印准备获取另一把锁的语句,然后进入死锁阻塞状态,程序不会自动终止。


内容的提问来源于stack exchange,提问作者Up Pu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 17:10:33