用一个简单的例子理解死锁

我正在努力理解死锁的基础知识,所以我想出了下面的代码。我有两个线程以相反的顺序获取锁,但它们没有死锁。当我运行它时,我会看到所有的打印输出。我究竟做错了什么?


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();

  }

}


HUWWW
浏览 149回答 4
4回答

宝慕林4294392

首先,没有保证哪个线程首先启动。要获得死锁,一个线程必须锁定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在第一个释放之前锁定它们

万千封印

您的代码是死锁的一个很好的例子,因为 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();

HUX布斯

两点:以与获取锁相反的顺序释放锁。也就是说,processThis应该颠倒删除锁的顺序。对于您的示例,顺序无关紧要。但是,如果processThis在释放 B 上的锁之前尝试在 A 上获取新锁,则可能会再次发生死锁。更一般地,您会发现通过考虑锁的范围并避免重叠但非封闭的范围来更容易考虑锁。为了更好地突出这个问题,我会wait在每个线程中获取第一个锁之后调用,并execute启动两个线程,然后在两个线程上调用notify。

红颜莎娜

这确实可能导致死锁,但并非总是如此,例如,如果 processThis() 完全执行,然后 processThat() 或反之亦然,则没有死锁。您可以尝试添加 Thread.delay(100) 或 Thread.yield() 来引导线程执行走向死锁,甚至解除对某个死锁的解锁。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java