为什么代码不会总是导致死锁,试图了解可重入锁与同步块的工作原理?

Why the code is not resulting in a deadlock ALWAYS, trying to understand the working of Reentrant locks vs synchronized blocks?

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

在第二个代码中,我尝试重现同样的问题,但这次使用 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();


    }

}

与 Reentrant Locks/not 相同的代码导致死锁

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

    }

}

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

请解释该行为。

您的两个代码片段不等价

第一个,你

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

第二个代码中(使用锁)

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

对于第二个,您正在降低两个线程为每个资源获取锁的可能性(线程 1 为资源 1 获取锁,线程 2 为资源 2 获取锁)。因此,这减少了它可能死锁的次数百分比。


要使第二个片段等同于第一个片段,

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

这应该与使用 同步 的代码一样经常死锁。 由于,这两种代码都容易出现死锁。