了解为什么在此实现中会发生死锁
Understanding why deadlock happens in this implementation
我是多线程的新手,遇到了这个例子:
public class TestThread {
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
ThreadDemo1 T1 = new ThreadDemo1();
ThreadDemo2 T2 = new ThreadDemo2();
T1.start();
T2.start();
}
private static class ThreadDemo1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}
private static class ThreadDemo2 extends Thread {
public void run() {
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 2...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}
这会导致以下示例输出:
Thread 1: Holding lock 1...
Thread 2: Holding lock 2...
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...
即出现死锁。但是,如果我们改变在第二个线程中获得锁的顺序,现在看起来像这样:
public class TestThread {
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
ThreadDemo1 T1 = new ThreadDemo1();
ThreadDemo2 T2 = new ThreadDemo2();
T1.start();
T2.start();
}
private static class ThreadDemo1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}
private static class ThreadDemo2 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}
它按预期工作,示例输出如下所示:
Thread 1: Holding lock 1...
Thread 1: Waiting for lock 2...
Thread 1: Holding lock 1 & 2...
Thread 2: Holding lock 1...
Thread 2: Waiting for lock 2...
Thread 2: Holding lock 1 & 2...
有人可以向我解释导致死锁的第一个代码中发生了什么,为什么第二个代码中的更改可以修复它?
好吧,在你的第一段代码中,第一个线程显然持有 Lock1
,第二个线程持有 Lock2
。然后它们试图获取彼此的锁,但它们失败了,因为其他锁已经被另一个线程持有,因此产生了死锁。
然而,在第二段代码中,第二个线程没有持有 Lock2
并且在第一个线程释放第一个锁之前被阻止。如您的输出所示,第一个线程在获取第二个锁后将释放第一个锁。
这是第一种情况的可能情况:
线程 1 获取 Lock1
并休眠 10 毫秒。现在线程 2 获取 Lock2
并进入休眠状态 10 毫秒。
现在线程 1 试图获取 Lock2
,但无法获取,因为它已被线程 2 获取,而线程 2 试图获取被线程 1 锁定的 Lock1
。
在第二种情况下,我们假设第一个线程被选为 运行。它获得 Lock1
,而另一个线程被阻塞,因为它也试图获得 Lock1
。现在线程 1 进入睡眠状态,并继续执行第二个(嵌套的)同步块。它试图获得它(因为它仍然是免费的)——现在他获得了 2 锁。另一个线程仍然被阻塞。
只有在它完成执行后,它才会释放两个锁(因为它退出了同步块),现在JVM可以自由决定选择哪个线程,但它不会真的很重要,同样的逻辑也会发生。
您在这里看到的是锁排序,一种常见的死锁预防方法。
在第一种情况下,考虑以下执行顺序,当前指令指针位于每个线程的标记位置:
Thread 1:
obtain lock 1
===>
obtain lock 2
Thread 2:
obtain lock 2
===>
obtain lock 1
现在,线程 1 接下来尝试获取锁 2,但是不能,因为锁 2 被线程 2 持有。线程 2 接下来尝试获取锁 1,但是不能,因为它被线程 1 持有。这是经典的循环资源依赖,因此导致死锁。
防止这种情况的一种全局方法是确保所有锁都具有 总 顺序,并且始终以该总顺序获取锁。
这个工作的证据很简单:因为所有锁依赖项在总顺序中都是 "downward",所以你不能有锁周期。
我是多线程的新手,遇到了这个例子:
public class TestThread {
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
ThreadDemo1 T1 = new ThreadDemo1();
ThreadDemo2 T2 = new ThreadDemo2();
T1.start();
T2.start();
}
private static class ThreadDemo1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}
private static class ThreadDemo2 extends Thread {
public void run() {
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 2...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}
这会导致以下示例输出:
Thread 1: Holding lock 1...
Thread 2: Holding lock 2...
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...
即出现死锁。但是,如果我们改变在第二个线程中获得锁的顺序,现在看起来像这样:
public class TestThread {
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
ThreadDemo1 T1 = new ThreadDemo1();
ThreadDemo2 T2 = new ThreadDemo2();
T1.start();
T2.start();
}
private static class ThreadDemo1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}
private static class ThreadDemo2 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1...");
try { Thread.sleep(10); }
catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}
它按预期工作,示例输出如下所示:
Thread 1: Holding lock 1...
Thread 1: Waiting for lock 2...
Thread 1: Holding lock 1 & 2...
Thread 2: Holding lock 1...
Thread 2: Waiting for lock 2...
Thread 2: Holding lock 1 & 2...
有人可以向我解释导致死锁的第一个代码中发生了什么,为什么第二个代码中的更改可以修复它?
好吧,在你的第一段代码中,第一个线程显然持有 Lock1
,第二个线程持有 Lock2
。然后它们试图获取彼此的锁,但它们失败了,因为其他锁已经被另一个线程持有,因此产生了死锁。
然而,在第二段代码中,第二个线程没有持有 Lock2
并且在第一个线程释放第一个锁之前被阻止。如您的输出所示,第一个线程在获取第二个锁后将释放第一个锁。
这是第一种情况的可能情况:
线程 1 获取 Lock1
并休眠 10 毫秒。现在线程 2 获取 Lock2
并进入休眠状态 10 毫秒。
现在线程 1 试图获取 Lock2
,但无法获取,因为它已被线程 2 获取,而线程 2 试图获取被线程 1 锁定的 Lock1
。
在第二种情况下,我们假设第一个线程被选为 运行。它获得 Lock1
,而另一个线程被阻塞,因为它也试图获得 Lock1
。现在线程 1 进入睡眠状态,并继续执行第二个(嵌套的)同步块。它试图获得它(因为它仍然是免费的)——现在他获得了 2 锁。另一个线程仍然被阻塞。
只有在它完成执行后,它才会释放两个锁(因为它退出了同步块),现在JVM可以自由决定选择哪个线程,但它不会真的很重要,同样的逻辑也会发生。
您在这里看到的是锁排序,一种常见的死锁预防方法。
在第一种情况下,考虑以下执行顺序,当前指令指针位于每个线程的标记位置:
Thread 1:
obtain lock 1
===>
obtain lock 2
Thread 2:
obtain lock 2
===>
obtain lock 1
现在,线程 1 接下来尝试获取锁 2,但是不能,因为锁 2 被线程 2 持有。线程 2 接下来尝试获取锁 1,但是不能,因为它被线程 1 持有。这是经典的循环资源依赖,因此导致死锁。
防止这种情况的一种全局方法是确保所有锁都具有 总 顺序,并且始终以该总顺序获取锁。
这个工作的证据很简单:因为所有锁依赖项在总顺序中都是 "downward",所以你不能有锁周期。