45

重入意味着锁是在每个线程而不是每个调用的基础上获取的。

既然内在锁是由一个线程持有的,那不是说一个线程运行一次就等于一个调用基础吗?

谢谢,这似乎意味着:在一个线程中,如果我lockA在处理doA调用函数的函数时得到一个锁doB,并且doB还需要一个锁lockA,那么就会有重入。在Java中,这种现象是每个线程获得的,所以我不需要考虑死锁?

4

6 回答 6

71

重入意味着锁是在每个线程而不是每个调用的基础上获取的。

这是一个误导性的定义。这是真的(有点),但它错过了真正的重点。

重入意味着(在一般的 CS / IT 术语中)你做某事,当你还在做的时候,你再做一次。在锁定的情况下,这意味着您在单个线程上执行以下操作:

  1. 获取对“foo”的锁定。
  2. 做一点事
  3. 获取对“foo”的锁定。请注意,我们还没有释放我们之前获得的锁。
  4. ...
  5. 释放对“foo”的锁定
  6. ...
  7. 释放对“foo”的锁定

使用可重入锁/锁定机制,获取相同锁的尝试将成功,并将增加属于该锁的内部计数器。只有当当前持有者两次释放锁时,才会释放锁。

这是Java中使用原始对象锁/监视器的示例……它们是可重入的:

Object lock = new Object();
...
synchronized (lock) {
    ...
    doSomething(lock, ...)
    ...
}

public void doSomething(Object lock, ...) {
    synchronized (lock) {
        ...
    }
}

可重入的替代方案是不可重入锁定,其中线程尝试获取它已经持有的锁将是错误的。

使用可重入锁的优点是您不必担心由于意外获取您已经持有的锁而导致失败的可能性。缺点是您不能假设您调用的任何内容都不会改变锁旨在保护的变量的状态。但是,这通常不是问题。锁通常用于防止其他线程进行并发状态更改。


所以我不需要考虑死锁?

是的你是。

线程不会对自己死锁(如果锁是可重入的)。但是,如果有其他线程可能锁定了您尝试锁定的对象,则可能会出现死锁。

于 2013-05-12T04:40:37.653 回答
22

想象一下这样的事情:

function A():
   lock (X)
       B()
   unlock (X)

function B():
    A()

现在我们调用 A。会发生以下情况:

  • 我们输入 A,锁定 X
  • 我们进入B
  • 我们再次进入A,再次锁定X

由于我们从未退出 A 的第一次调用,因此 X 仍处于锁定状态。这称为重新进入 - 当函数 A 尚未返回时,再次调用函数 A。如果 A 依赖于一些全局的静态状态,这可能会导致“重新进入错误”,在从函数的出口清除静态状态之前,函数会再次运行,并且一半的计算值与第二次通话。

在这种情况下,我们遇到了我们已经持有的锁。如果锁是可重入感知的,它将意识到我们是同一个线程已经持有锁并让我们通过。否则,它将永远死锁——它将等待它已经持有的锁。

在java中,lock并且synchronized是可重入的——如果一个锁被一个线程持有,并且该线程试图重新获取相同的锁,它是被允许的。所以如果我们用Java写上面的伪代码,就不会死锁了。

于 2013-05-12T04:47:10.083 回答
12

实践书中的 Java 并发性 -Reentrancy means that locks are acquired on a per-thread rather than per-invocation basis.

让我解释一下它的确切含义。首先,内在锁本质上是可重入的。实现重入的方法是维护一个计数器,用于获取锁的数量和锁的所有者。如果计数为 0 并且没有与之关联的所有者,则意味着锁没有被任何线程持有。当一个线程获得锁时,JVM记录所有者并将计数器设置为1。如果同一线程再次尝试获取锁,则计数器增加。当拥有线程退出同步块时,计数器递减。当 count 再次达到 0 时,锁被释放。

一个简单的例子是 -

public class Test {
    public synchronized void performTest() {
       //...
    }
}

public class CustomTest extends Test {
    public synchronized void performTest() {
       //...
       super.performTest();
    }
}

如果没有重入,就会出现僵局。

在此处输入图像描述

于 2016-06-07T04:01:36.077 回答
4

重入意味着锁是在每个线程而不是每个调用的基础上获取的。

让我用一个例子来解释这一点。

class ReentrantTester {

    public synchronized void methodA() {
      System.out.println("Now I am inside methodA()");
      methodB();
    }

    public synchronized void methodB() {
      System.out.println("Now I am inside methodB()");
    }

    public static void main(String [] args) {
        ReentrantTester rt = new ReentrantTester();
        rt.methodA();  
    }

}

输出是:

Now I am inside methodA()
Now I am inside methodB()

如上代码,ReentrantTester 包含两个同步方法:methodA() & methodB() 第一个同步方法methodA() 调用另一个同步方法methodB()。

当执行进入 methodA() 时,当前线程获取 ReentrantTester 对象的监视器。现在当methodA()调用methodB()时,因为methodB()也是同步的,线程会再次尝试获取同一个监视器。因为 Java 支持可重入监视器,所以这是可行的。当前线程再次获取 ReentrantTester 的监视器并继续执行 methodA() 和 methodB()。

Java 运行时允许线程重新获取它已经拥有的监视器,因为 Java 监视器是可重入的。这些可重入监视器很重要,因为它们消除了单线程在它已经拥有的监视器上自行死锁的可能性。

于 2019-02-10T06:18:40.340 回答
2

这只是意味着一旦线程获得了锁,它就可以根据需要多次进入锁定的代码部分。因此,如果您有一个同步的代码部分,例如一个方法,那么只有获得锁的线程可以调用该方法,但可以根据需要多次调用该方法,包括同一锁持有的任何其他代码。如果您有一个方法调用另一个方法,并且两者都由同一个锁同步,这一点很重要。如果不是这样的话。第二个方法调用会阻塞。它也适用于递归方法调用。

public void methodA()
{
     // other code
     synchronized(this)
     {
          methodB();
     } 
}

public void methodB()
{
     // other code
     syncrhonized(this)
     {
          // it can still enter this code    
     }

}
于 2013-05-12T04:37:01.627 回答
0

这是关于递归,想想:

private lock = new ReentrantLock();
public void method() {
      lock.lock();
      method();
}

如果锁不能重入,线程可能会阻塞自己。

于 2020-04-11T07:10:40.343 回答