В чем смысл ReentrantLock в Java?

Повторный вход означает, что блокировки устанавливаются для каждого потока, а не для каждого вызова.

Поскольку внутренняя блокировка удерживается потоком, не означает ли это, что поток, запущенный один раз, равен основе вызова?

Спасибо, кажется, это означает, что: в потоке, если я получаю блокировку lockA при обработке функции doA, которая вызывает функцию doB, а doB также требуется блокировка lockA, тогда будет повторный вход. В Java это явление приобретается для каждого потока, поэтому мне не нужно учитывать взаимоблокировки?


person znlyj    schedule 12.05.2013    source источник
comment
Представьте, если вход в метод блокирует объект X, а в этом методе вы вызываете тот же самый метод (либо напрямую, либо дальше в стеке вызовов) и снова блокируете объект X. Что случается? Ожидаете ли вы блокировку, которую вы уже удерживаете, или она знает, что поток, удерживающий ее, также является потоком, вызывающим ее снова, и позволяет ей пройти? Это называется повторным входом — вы вводите тот же метод, который вы уже использовали ранее.   -  person Patashu    schedule 12.05.2013
comment
Да, синхронизированные блоки и блокировки в Java являются повторно входящими, поэтому, как только поток foo имеет блокировку блокировки, он может безопасно вызывать методы, которые также блокируют блокировку, без необходимости предварительно разблокировать ее.   -  person Patashu    schedule 12.05.2013
comment
Вы по-прежнему должны учитывать взаимоблокировку, взаимоблокировка может возникнуть, когда два потока ждут друг друга.   -  person rubixibuc    schedule 12.05.2013
comment
да, я имею в виду мою ситуацию выше.   -  person znlyj    schedule 12.05.2013


Ответы (6)


Повторный вход означает, что блокировки устанавливаются для каждого потока, а не для каждого вызова.

Это вводящее в заблуждение определение. Это правда (вроде как), но это упускает из виду реальную суть.

Реентерабельность означает (в общей терминологии 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) {
        ...
    }
}

Альтернативой повторному вхождению является блокировка без повторного входа, при которой попытка потока получить блокировку, которую он уже удерживает, будет ошибкой.

Преимущество использования повторных блокировок заключается в том, что вам не нужно беспокоиться о возможности сбоя из-за случайного получения блокировки, которая у вас уже есть. Недостатком является то, что вы не можете предположить, что ничто из того, что вы вызываете, не изменит состояние переменных, для защиты которых предназначена блокировка. Однако обычно это не проблема. Блокировки обычно используются для защиты от одновременных изменений состояния, сделанных другими потоками.


Так что мне не нужно рассматривать взаимоблокировки?

Да, вы делаете.

Поток не будет блокироваться сам по себе (если блокировка является повторной). Однако вы можете получить взаимоблокировку, если есть другие потоки, которые могут заблокировать объект, который вы пытаетесь заблокировать.

person Stephen C    schedule 12.05.2013

Представьте что-то вроде этого:

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

function B():
    A()

Теперь вызываем А. Происходит следующее:

  • Вводим A, запираем X
  • Мы входим в Б
  • Мы снова вводим A, снова запираем X

Поскольку мы так и не вышли из первого вызова A, X все еще заблокирован. Это называется повторным входом — пока функция А еще не вернулась, функция А вызывается снова. Если A полагается на какое-то глобальное статическое состояние, это может вызвать «ошибку повторного входа», когда до того, как статическое состояние будет очищено от выхода функции, функция запускается снова, и половина вычисленных значений сталкивается с началом второй звонок.

В этом случае мы упираемся в блокировку, которую уже держим. Если блокировка знает о повторном входе, она поймет, что мы являемся тем же потоком, который уже удерживает блокировку, и пропустит нас. В противном случае он заблокируется навсегда - он будет ждать блокировки, которую уже удерживает.

В java lock и synchronized осведомлены о повторном входе - если блокировка удерживается потоком, и поток пытается повторно получить ту же блокировку, это разрешено. Поэтому, если бы мы написали приведенный выше псевдокод на Java, это не привело бы к взаимоблокировке.

person Patashu    schedule 12.05.2013

Параллелизм 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();
    }
}

без повторного входа был бы тупик.

введите здесь описание изображения

person Aniket Thakur    schedule 07.06.2016

Повторный вход означает, что блокировки устанавливаются для каждого потока, а не для каждого вызова.

Позвольте мне объяснить это на примере.

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

Когда выполнение входит в метод A(), текущий поток получает монитор для объекта ReentrantTester. Теперь, когда метод A() вызывает метод B(), поскольку метод B() также синхронизирован, поток пытается снова получить тот же монитор. Поскольку Java поддерживает реентерабельные мониторы, это работает. Текущий поток снова получает монитор ReentrantTester и продолжает выполнение как метода A(), так и метода B().

Среда выполнения Java позволяет потоку повторно захватить монитор, который у него уже есть, поскольку мониторы Java являются реентерабельными. Эти реентерабельные мониторы важны, потому что они исключают возможность взаимной блокировки отдельного потока на мониторе, который он уже удерживает.

person Jeewantha Samaraweera    schedule 10.02.2019

Это просто означает, что когда поток имеет блокировку, он может входить в заблокированный раздел кода столько раз, сколько ему нужно. Таким образом, если у вас есть синхронизированный раздел кода, такой как метод, только поток, достигший блокировки, может вызвать этот метод, но может вызывать этот метод столько раз, сколько захочет, включая любой другой код, удерживаемый той же блокировкой. Это важно, если у вас есть один метод, который вызывает другой метод, и оба синхронизируются одной и той же блокировкой. Если бы это было не так. Второй вызов метода будет заблокирован. Это также применимо к рекурсивным вызовам методов.

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

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

}
person rubixibuc    schedule 12.05.2013

это о рекурсии, подумайте о:

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

Если блокировка не допускает повторного входа, поток может заблокироваться сам.

person igonejack    schedule 11.04.2020