Java中的可重入锁和死锁

8

有人能向我解释一下在Java代码(伪代码)示例中,Reentrant lockdeadlock之间的关系吗?

4个回答

14

可重入锁机制允许持有锁的线程重新进入临界区。这意味着你可以像这样做:

public synchronized void functionOne() {

    // do something

    functionTwo();

    // do something else

    // redundant, but permitted...
    synchronized(this) {
        // do more stuff
    }    
}

public synchronized void functionTwo() {
     // do even more stuff!
}

在一个不可重入锁中,当你试图从functionOne()调用functionTwo()时,会发生死锁情况,因为线程必须等待自己持有的锁,导致无法继续执行。

死锁通常是这样一种恶劣情形:线程 1 持有锁 A 并等待锁 B,而线程 2 持有锁 B 并等待锁 A。因此,两个线程都无法继续执行。以下示例代码会创建死锁:

public synchronized void deadlock() throws InterruptedException {
    Thread th = new Thread() {
        public void run() {
            deadlock();
        }
    }.start();

    th.join();
}

调用线程试图等待已生成的线程,而生成的线程又不能调用 deadlock() 直到调用者退出。炸裂!


我猜OP的意思是使用类java.util.concurrent.locks.ReentrantLock而不是synchronized块的示例。 - Victor Sorokin
@Victor Sorokin 我不确定他是否意味着要进行资本化。无论是使用synchronized块还是ReentrantLock类,高级概念都是相同的。 - stevevls

6

死锁发生在一个线程等待永远不会变为真的条件时。

显而易见的情况是,当您尝试以不同的顺序锁定两个锁时,由不同的线程锁定。

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

public void methodA() {
    lock1.lock();
    lock2.lock();
    // do something and unlock both.
}

public void methodB() {
    lock2.lock();
    lock1.lock();
    // do something and unlock both.
}

如您所见,一个线程可以调用methodA并获得lock1,等待lock2,另一个线程可以调用methodB并获得lock2,等待lock1。


然而,一个线程也可能发生死锁。一个例子是ReentrantReadWriteLock因为它不支持将读锁升级为写锁。

ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.readLock().lock();
// do we need to update?
rwl.writeLock().lock(); // will wait for the readLock() to be released!

一种使自己陷入死锁的隐蔽机会是使用隐含锁。静态初始化器块是隐式线程安全的,因此即使静态初始化器块没有同步,也会使用锁。

class A {
     private static int VALUE;
     static {
        Thread t = new Thread() {
            public void run() {
                // waits for the A class to load.
                VALUE = someLongTask();
            }
        };
        t.start();
        // waits for the thread.
        t.join();
    }
}

你又遇到了死锁!


1

这是一个使用ReentrantLock演示死锁的例子

class Deadlock {
    private static final ReentrantLock l1 = new ReentrantLock();

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            public void run() {
                System.out.println("A Trying to lock...");
                l1.lock();
                System.out.println("A Locked...");
                try {
                    Thread t = new Thread(new Runnable() {
                        public void run() {
                            System.out.println("B Trying to lock...");
                            l1.lock();
                            System.out.println("B Must not print");
                            try {
                            } finally {
                                System.out.println("B Trying to unlock...");
                                l1.unlock();
                                System.out.println("B Unlocked...");
                            }
                        }
                    });
                    t.start();
                    try {
                        t.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    System.out.println("A Trying to unlock...");
                    l1.unlock();
                    System.out.println("A Unlocked...");
                }
            }
        });
        t.start();
    }
}

为了解决死锁问题,请注释掉调用t.join的语句,以及包含它的try/catch块。

0

可重入锁允许锁持有者在已经通过进入其他代码块获得锁之后,再次进入代码块。非可重入锁将会阻塞锁持有者自身,因为它必须释放从另一个代码块获得的锁,以重新获取相同的锁来进入嵌套锁定所需的代码块。

就死锁而言,如果您从受保护的代码块调用另一个受保护的代码块,则需要可重入锁(否则您将在等待自己时发生死锁)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接