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

时间:2019-02-18 19:03:33

标签: java multithreading deadlock 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
  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();
  }
}

4 个答案:

答案 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)

两点:

  1. 发布以相反的顺序锁定它们。也就是说,processThis应该颠倒删除锁的顺序。对于您的示例,顺序无关紧要。但是,如果processThis在释放对B的锁定之前尝试获取对A的新锁定,则可能再次发生死锁。一般来说,通过考虑锁的作用域并避免重叠但不封闭的作用域,您会更容易考虑锁。
  2. 为了更好地说明问题,在获取每个线程的第一个锁之后,我将调用wait,并让execute启动两个线程然后调用{两个线程上的{1}}。