哲学家就餐难题的现代解法:用ReentrantLock的tryLock()策略性规避死锁
想象一下,五位哲学家围坐在一张圆桌旁,每人面前都有一碗意大利面,但桌上只有五把叉子,每两位哲学家之间共享一把。要吃到面,一位哲学家必须同时拿起他左右两边的叉子。如果每个人都同时拿起左边的叉子,然后等待右边的叉子被释放,那么所有人都会陷入无尽的等待——这就是经典的“哲学家就餐问题”,一个在计算机科学中描述资源竞争和死锁的绝佳隐喻。
在实际的多线程开发中,这种因循环等待资源而导致的“僵局”并不罕见。当多个线程竞争一组锁,且每个线程都持有一部分资源并等待其他线程释放剩余资源时,系统就可能陷入停滞。传统的synchronized关键字或简单的lock()方法,在无法获取锁时会无限期阻塞,这正是死锁的温床。而ReentrantLock提供的tryLock()方法,特别是其带超时参数的版本,为我们提供了一把打破僵局的钥匙。它允许线程“尝试”获取锁,如果失败,可以优雅地退让,释放已持有的资源,从而破坏死锁的必要条件之一——“不可剥夺”。这不仅仅是API的简单调用,更是一种从“阻塞等待”到“主动试探与退让”的并发设计哲学转变。
1. 理解死锁:从哲学家的困境到线程的僵局
死锁并非多线程编程中的抽象概念,它源于一个非常具体的场景:多个执行单元(线程或进程)因竞争有限资源而陷入相互等待的循环。要形成死锁,通常需要同时满足四个必要条件,这被称为 Coffman条件:
- 互斥:资源一次只能被一个线程占用。
- 持有并等待:线程在持有至少一个资源的同时,等待获取其他线程持有的资源。
- 不可剥夺:线程已获得的资源在未使用完之前,不能被强制剥夺。
- 循环等待:存在一个线程资源的循环等待链。
哲学家就餐问题完美地诠释了这四个条件。在代码层面,一个典型的死锁可能看起来像下面这样简单:
public class SimpleDeadlock {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lockA) {
System.out.println("Thread1 acquired lockA");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lockB) {
System.out.println("Thread1 acquired lockB");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lockB) {
System.out.println("Thread2 acquired lockB");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lockA) {
System.out.println("Thread2 acquired lockA");
}
}
});
thread1.start();
thread2.start();
}
}
运行这段代码,你很可能会看到程序打印出前两条信息后便永远挂起,两个线程各自持有一把锁,并无限期地等待对方释放另一把锁。
注意:死锁在开发环境或测试中可能因线程调度时机不同而时隐时现,这使其更难以捉摸和调试。线上环境一旦发生,往往直接导致服务部分或全部不可用。
为了诊断死锁,JDK提供了强大的工具。你可以使用jstack命令或jconsole等可视化工具来获取线程转储。在转储信息中,死锁线程会明确地被标识出来。例如,jstack的输出可能包含如下部分:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f8b1c0078e8 (object 0x000000076ab45c20, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007f8b1c007b68 (object 0x000000076ab45c30, a java.lang.Object),
which is held by "Thread-1"
这个报告清晰地指出了两个线程相互等待对方持有锁的循环依赖关系。



被折叠的 条评论
为什么被折叠?



