如何解决Java死锁问题并让两个线程正常执行?能否用wait/notify实现?
解决Java死锁问题并实现线程正常执行
嘿,你写的这段代码确实触发了经典的循环等待死锁:线程t1持有lock1并等待lock2,线程t2持有lock2并等待lock1,两者都不肯释放手里的锁,直接卡住了。下面给你几种实用的解决方案,包括你问的wait/notify实现方式。
一、最简单的解决方法:统一锁的获取顺序
死锁的核心诱因之一就是线程获取锁的顺序不一致,只要让两个线程都按照相同的顺序获取锁(比如先拿lock1,再拿lock2),就能彻底避免死锁。
修改后的代码:
class MyCode { public static Object lock1 = new Object(); public static Object lock2 = new Object(); public static void main(String[] args) throws Exception { Thread t1 = new Thread(() -> { synchronized (lock1) { System.out.println("Took lock on object" + lock1); try { Thread.sleep(1000); // 缩短睡眠时长方便快速测试 } catch (Exception e) {} synchronized (lock2) { System.out.println("hello world 1...."); } } }); Thread t2 = new Thread(() -> { // 这里改成和t1一致的锁获取顺序:先lock1,再lock2 synchronized (lock1) { System.out.println("Took lock on object" + lock1); try { Thread.sleep(1000); } catch (Exception e) {} synchronized (lock2) { System.out.println("hello world 2...."); } } }); t1.start(); t2.start(); System.out.println("Hello Java"); } }
这样两个线程会依次获取lock1,再获取lock2,绝不会出现互相等待对方锁的情况。
二、使用显式锁(ReentrantLock)的tryLock避免死锁
如果业务场景不允许统一锁顺序,可以用ReentrantLock的tryLock(long timeout, TimeUnit unit)方法,在指定时间内获取不到锁就主动放弃,避免一直阻塞。
示例代码:
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; class MyCode { public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); public static void main(String[] args) throws Exception { Thread t1 = new Thread(() -> { try { if (lock1.tryLock(1, TimeUnit.SECONDS)) { System.out.println("Took lock on object" + lock1); try { Thread.sleep(1000); // 尝试获取lock2,超时就放弃并释放已持有的lock1 if (lock2.tryLock(1, TimeUnit.SECONDS)) { try { System.out.println("hello world 1...."); } finally { lock2.unlock(); } } else { System.out.println("t1 failed to get lock2, releasing lock1"); } } finally { lock1.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); Thread t2 = new Thread(() -> { try { if (lock2.tryLock(1, TimeUnit.SECONDS)) { System.out.println("Took lock on object" + lock2); try { Thread.sleep(1000); if (lock1.tryLock(1, TimeUnit.SECONDS)) { try { System.out.println("hello world 2...."); } finally { lock1.unlock(); } } else { System.out.println("t2 failed to get lock1, releasing lock2"); } } finally { lock2.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); t1.start(); t2.start(); System.out.println("Hello Java"); } }
这种方式更灵活,线程不会无限等待锁,超时后会释放已持有的锁,从根源上避免死锁。
三、使用wait/notify实现线程协调
当然可以用wait/notify来解决这个问题!核心思路是通过等待通知机制,让两个线程协调获取锁的时机,避免同时持有对方需要的锁。
我们可以引入一个共享的监视器对象,让线程t1在获取lock1后,等待t2获取lock2并发出通知,再尝试获取lock2;反之亦然。示例代码如下:
class MyCode { public static Object lock1 = new Object(); public static Object lock2 = new Object(); // 共享监视器,用于wait/notify的同步 public static Object monitor = new Object(); // 标记t2是否已获取lock2 public static boolean t2GotLock2 = false; public static void main(String[] args) throws Exception { Thread t1 = new Thread(() -> { synchronized (lock1) { System.out.println("Took lock on object" + lock1); try { Thread.sleep(1000); // 等待t2获取lock2的通知,用while循环避免虚假唤醒 synchronized (monitor) { while (!t2GotLock2) { monitor.wait(); } } // 此时t2已持有lock2,安全获取lock2 synchronized (lock2) { System.out.println("hello world 1...."); } } catch (Exception e) { Thread.currentThread().interrupt(); } } }); Thread t2 = new Thread(() -> { synchronized (lock2) { System.out.println("Took lock on object" + lock2); try { Thread.sleep(1000); // 通知t1:我已经拿到lock2了 synchronized (monitor) { t2GotLock2 = true; monitor.notify(); } // 安全获取lock1 synchronized (lock1) { System.out.println("hello world 2...."); } } catch (Exception e) { Thread.currentThread().interrupt(); } } }); t1.start(); t2.start(); System.out.println("Hello Java"); } }
注意:wait/notify必须在同步块中调用,而且要用while循环检查条件(避免JVM的虚假唤醒)。这个例子中,两个线程通过监视器对象协调锁的获取时机,彻底避免了死锁。
内容的提问来源于stack exchange,提问作者Nicky
相关产品推荐
相关产品推荐

