【问题标题】:Why the code is not resulting in a deadlock ALWAYS, trying to understand the working of Reentrant locks vs synchronized blocks?为什么代码总是不会导致死锁,试图了解可重入锁与同步块的工作原理?
【发布时间】:2020-05-10 08:11:21
【问题描述】:

我有两段代码。第一个使用同步块,它会导致死锁,这是完全可以理解的。

在第二个代码中,我尝试重新创建相同的问题,但这次使用Reentrant locks。但是第二个代码在某些情况下不会导致死锁。在某些情况下会出现死锁,控制台中不会打印任何内容。

你能解释一下为什么吗?我没有正确使用Reentrant locks 吗?

导致死锁的代码

package com.practice.multithreading;

public class DeadlockYoutube {

    public static void main(String[] args) {

        final String resource1="Printer";
        final String resource2="Scanner";

        Runnable run1=()->{
            synchronized (resource1) {
                System.out.println(Thread.currentThread().getName()+" : locked-> "+resource1);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized(resource2) {
                    System.err.println(Thread.currentThread().getName()+" : locked-> "+resource2);
                }
            }
        };

        Runnable run2=new Runnable(){
            @Override
            public void run() {
                synchronized (resource2) {
                    System.out.println(Thread.currentThread().getName()+" : locked-> "+resource2);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized(resource1) {
                        System.err.println(Thread.currentThread().getName()+" : locked-> "+resource1);
                    }
                }   
            }

        };

        Thread thread1= new Thread(run1);
        thread1.setName("Desktop");
        Thread thread2=new Thread(run2);
        thread2.setName("Laptop");

        thread1.start();
        thread2.start();


    }

}

具有可重入锁的相同代码/不会导致死锁

package com.practice.multithreading;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


//not working as expected
public class DeadlockYoutubeReentrantLocks {

    public static void main(String[] args) {

        final String resource1 = "Printer";
        final String resource2 = "Scanner";

        Lock lock1 = new ReentrantLock();
        Lock lock2 = new ReentrantLock();

        Runnable run1 = () -> {
            lock1.lock();
            lock2.lock();
            System.out.println(Thread.currentThread().getName() + " : locked-> " + resource1);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.err.println(Thread.currentThread().getName() + " : locked-> " + resource2);

            lock1.unlock();
            lock2.unlock();

        };

        Runnable run2 = new Runnable() {
            @Override
            public void run() {
                lock2.lock();
                lock1.lock();
                System.out.println(Thread.currentThread().getName() + " : locked-> " + resource2);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.err.println(Thread.currentThread().getName() + " : locked-> " + resource1);
                lock2.unlock();
                lock1.unlock();
            }

        };

        Thread thread1 = new Thread(run1);
        thread1.setName("Desktop");
        Thread thread2 = new Thread(run2);
        thread2.setName("Laptop");

        thread1.start();
        thread2.start();

    }

}

我尝试颠倒锁的顺序..但代码有时执行得很好,有时会出现死锁,控制台中什么也没有。

请解释行为。

【问题讨论】:

  • 在这两种情况下,死锁都可能发生或不发生。您无法控制线程的调度方式。如果死锁没有发生,并不意味着它不会发生。你应该以一种确保它永远不会发生的方式编写代码。
  • 但是我尝试连续多次运行第一个代码,它总是导致死锁,而第二个没有。

标签: java multithreading deadlock synchronized reentrantlock


【解决方案1】:

你的两个代码 sn-ps 不等效。

在第一个,你

  1. 获取资源 1 的锁定
  2. 睡觉
  3. 获取资源 2 的锁

在第二个代码中(使用锁)

  1. 获取资源 1 的锁定
  2. 获取资源 2 的锁
  3. 睡觉

使用第二个,您正在降低两个线程为每个资源获取锁的概率(线程 1 获取资源 1 的锁,线程 2 获取资源 2 的锁)。因此,这会减少死锁的次数。


为了使第二个 sn-p 等价于第一个,

Runnable run1 = () -> {
    lock1.lock();
    System.out.println(Thread.currentThread().getName() + " : locked-> " + resource1);
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    lock2.lock(); //<-- Moved here
    System.err.println(Thread.currentThread().getName() + " : locked-> " + resource2);

    lock1.unlock();
    lock2.unlock();

};

Runnable run2 = () -> {
    lock2.lock();
    System.out.println(Thread.currentThread().getName() + " : locked-> " + resource2);
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    lock1.lock(); //<-- Moved here
    System.err.println(Thread.currentThread().getName() + " : locked-> " + resource1);
    lock2.unlock();
    lock1.unlock();
};

这应该会像使用同步的代码一样经常死锁。 如commented by @michalk,这两个代码都容易死锁。

【讨论】:

  • 很好,我还要提一下,如果没有发生死锁,并不意味着它不存在。
  • @michalk 我已链接到您同意的评论。我确定 OP 并不意味着第二个代码比第一个代码好。这是一篇教育文章,旨在了解为什么第二个代码没有像第一个代码那样经常死锁。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-20
  • 2012-08-27
  • 1970-01-01
  • 1970-01-01
  • 2017-11-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多