为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

HUWWW 2022-06-23 19:28:37
我正在努力理解死锁的基础知识,所以我想出了下面的代码。我有两个线程以相反的顺序获取锁,但它们没有死锁。当我运行它时,我会看到所有的打印输出。我究竟做错了什么?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 回答

?
宝慕林4294392

TA贡献2021条经验 获得超8个赞

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


查看完整回答
反对 回复 2022-06-23
?
万千封印

TA贡献1891条经验 获得超3个赞

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


查看完整回答
反对 回复 2022-06-23
?
HUX布斯

TA贡献1876条经验 获得超6个赞

两点:

  1. 以与获取锁相反的顺序释放锁。也就是说,processThis应该颠倒删除锁的顺序。对于您的示例,顺序无关紧要。但是,如果processThis在释放 B 上的锁之前尝试在 A 上获取新锁,则可能会再次发生死锁。更一般地,您会发现通过考虑锁的范围并避免重叠但非封闭的范围来更容易考虑锁。

  2. 为了更好地突出这个问题,我会wait在每个线程中获取第一个锁之后调用,并execute启动两个线程,然后在两个线程上调用notify


查看完整回答
反对 回复 2022-06-23
?
红颜莎娜

TA贡献1842条经验 获得超13个赞

这确实可能导致死锁,但并非总是如此,例如,如果 processThis() 完全执行,然后 processThat() 或反之亦然,则没有死锁。您可以尝试添加 Thread.delay(100) 或 Thread.yield() 来引导线程执行走向死锁,甚至解除对某个死锁的解锁。



查看完整回答
反对 回复 2022-06-23
  • 4 回答
  • 0 关注
  • 148 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号