我正在研究死锁基础知识,因此我想出了以下代码。我有两个线程以相反的顺序获取锁,但是它们没有死锁。运行它时,我会看到所有打印输出。我在做什么错了?
public class DeadlockBasics {
private Lock lockA = new ReentrantLock();
private Lock lockB = new ReentrantLock();
public static void main(String[] args) {
DeadlockBasics dk = new DeadlockBasics();
dk.execute();
}
private void execute() {
new Thread(this::processThis).start();
new Thread(this::processThat).start();
}
// called by thread 1
public void processThis() {
lockA.lock();
// process resource A
System.out.println("resource A -Thread1");
lockB.lock();
// process resource B
System.out.println("resource B -Thread1");
lockA.unlock();
lockB.unlock();
}
// called by thread 2
public void processThat() {
lockB.lock();
// process resource B
System.out.println("resource B -Thread2");
lockA.lock();
// process resource A
System.out.println("resource A -Thread2");
lockA.unlock();
lockB.unlock();
}
}
答案 0 :(得分:2)
首先,没有保证首先启动哪个线程。要获得死锁,其中一个线程必须锁定lockA
,然后第二个线程必须锁定lockB
,反之亦然。
public void processThis() {
lockA.lock();
// here the control should be switched to another thread
System.out.println("resource A -Thread1");
lockB.lock();
...
但是可能没有足够的时间在线程之间进行切换,因为您只有几行代码。这太快了。要模拟一些长时间的工作,请在两个方法的第二次锁定之前添加延迟
lockA.lock();
Thread.sleep(200); // 200 milis
然后第二个线程将能够在第一个线程都释放lockB
之前将其锁定
答案 1 :(得分:1)
这确实可能导致死锁,但并非总是如此,例如,如果processThis()被完全执行,然后processThat()或反之则没有死锁。您可以尝试添加Thread.delay(100)或Thread.yield()来将线程的执行导向死锁,甚至将解锁解除到某个死锁。
答案 2 :(得分:1)
您的代码是死锁的一个很好的例子,因为ReenttrantLock是互斥锁,其行为与使用同步的隐式监视器锁访问相同。但是,由于这一部分,您看不到僵局:
private void execute() {
new Thread(this::processThis).start();
new Thread(this::processThat).start();
}
创建并启动第一个线程后,需要一段时间才能创建第二个线程。 JVM大约需要50 us甚至更少的时间来创建一个新线程,这听起来很短,但是足以完成第一个线程,因此不会发生死锁。
我在您的代码中添加了Thread.sleep();
,以便可以并行执行两个线程。
package com.company;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockBasics {
private Lock lockA = new ReentrantLock();
private Lock lockB = new ReentrantLock();
public static void main(String[] args) {
DeadlockBasics dk = new DeadlockBasics();
dk.execute();
}
private void execute() {
new Thread(this::processThis).start();
new Thread(this::processThat).start();
}
// called by thread 1
private void processThis() {
lockA.lock();
// process resource A
try {
Thread.sleep(1000); //Wait for thread 2 to be executed
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 will own lock a");
lockB.lock();
// process resource B
System.out.println("Thread 1 will own lock b");
lockA.unlock();
lockB.unlock();
// Both locks will now released from thread 1
}
// called by thread 2
private void processThat() {
lockB.lock();
// process resource B
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2 will own lock b");
lockA.lock();
// process resource A
System.out.println("Thread 2 will own lock a");
lockA.unlock();
lockB.unlock();
// Both locks are released by thread 2
}
}
答案 3 :(得分:1)
两点:
processThis
应该颠倒删除锁的顺序。对于您的示例,顺序无关紧要。但是,如果processThis
在释放对B的锁定之前尝试获取对A的新锁定,则可能再次发生死锁。一般来说,通过考虑锁的作用域并避免重叠但不封闭的作用域,您会更容易考虑锁。wait
,并让execute
启动两个线程然后调用{两个线程上的{1}}。