为何含Thread.Sleep()的Java 8代码未触发死锁?
代码示例
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");
原因解析
CompletableFuture默认线程池的线程是守护线程
CompletableFuture.runAsync()默认使用ForkJoinPool.commonPool(),这个线程池中的线程属于守护线程。JVM的退出规则是:当所有非守护线程都执行完毕后,JVM会直接终止,不管守护线程是否还在运行。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

