Зачем использовать ReentrantLock, если вы можете использовать синхронизированный (это)?

Я пытаюсь понять, что делает блокировку в параллелизме настолько важной, если можно использовать synchronized (this). В коде ниже, я могу сделать либо:

  1. синхронизировать весь метод или синхронизировать уязвимую область (synchronized(this){...})
  2. ИЛИ заблокируйте уязвимую область кода с помощью ReentrantLock.

Код:

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}

Ответ 1

A ReentrantLock неструктурирован, в отличие от конструкций synchronized - то есть вам не нужно использовать структуру блоков для блокировки и может даже удерживать блокировку между методами. Пример:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

Такой поток невозможно представить через один монитор в конструкции synchronized.


Кроме того, ReentrantLock поддерживает блокировать опрос и прерывая блокировка ожидания, которые поддерживают тайм-аут. ReentrantLock также поддерживает настраиваемую политику справедливости, позволяющую более гибкое планирование потоков.

Конструктор этого класса принимает необязательный параметр справедливости. Когда установлено true, в конфликте блокировки предпочитают предоставление доступа к самому длинному ожидающему потоку. В противном случае этот замок не гарантирует какой-либо конкретный порядок доступа. Программы, использующие справедливые блокировки, к которым обращаются многие потоки, могут отображать более низкую общую пропускную способность (т.е. Медленнее, часто намного медленнее), чем те, которые используют настройку по умолчанию, но имеют меньшие отклонения во времени, чтобы получить блокировки и гарантировать отсутствие голода. Обратите внимание, однако, что справедливость блокировок не гарантирует справедливость планирования потоков. Таким образом, один из многих потоков, использующих справедливую блокировку, может получать его несколько раз подряд, тогда как другие активные потоки не развиваются и в настоящее время не удерживают блокировку. Также обратите внимание, что метод untimed tryLock не соблюдает настройку справедливости. Это будет успешным, если блокировка доступна, даже если ожидаются другие потоки.


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

Однако эта претензия была оспорена; см. следующий комментарий:

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


Когда вы должны использовать ReentrantLock s? Согласно этой статье developerWorks...

Ответ довольно прост - используйте его, когда вам действительно нужно что-то, что он предоставляет, synchronized не работает, например, время ожидания блокировки, прерывание блокировки, блокировки с неструктурированной структурой, множество переменных условий или блокировка опроса, ReentrantLock также обладает преимуществами масштабируемости, и вы должны использовать его, если у вас действительно есть ситуация, которая демонстрирует высокий уровень конкуренции, но помните, что подавляющее большинство блоков synchronized вряд ли когда-либо демонстрируют какие-либо споры, не говоря уже о высоком соперничестве. Я бы посоветовал разработать с синхронизацией, пока синхронизация оказалась недостаточной, вместо того, чтобы просто предположить, что "производительность будет лучше", если вы используете ReentrantLock. Помните, что это расширенные инструменты для продвинутых пользователей. (И действительно продвинутые пользователи предпочитают самые простые инструменты, которые они могут найти, пока не убедятся в том, что простые инструменты неадекватны.) Как всегда, сначала сделайте это правильно, а затем волнуйтесь о том, нужно ли вам делать это быстрее.

Ответ 2

ReentrantReadWriteLock - это специализированная блокировка, тогда как synchronized(this) - блокировка общего назначения. Они похожи, но не совсем одинаковы.

Вы правы в том, что вы можете использовать synchronized(this) вместо ReentrantReadWriteLock но обратное не всегда верно.

Если вы хотите лучше понять, что делает ReentrantReadWriteLock особенным, посмотрите некоторую информацию о синхронизации потоков производителя-потребителя.

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

Кстати, использование synchronized(this) - и вообще блокировка с использованием экземпляра открытого класса - может быть проблематичной, потому что это открывает ваш код для потенциальных мертвых блокировок, потому что кто-то другой, неосознанно, может попытаться заблокировать ваш объект где-то еще в программа.

Ответ 3

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

 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!
 }

Расширенные возможности блокировки реентера включают: -

  • Возможность иметь более одной переменной условия для каждого монитора. Мониторы, использующие ключевое слово synchronized, могут иметь только один. Это означает, что блокировки реентера поддерживают более одной очереди wait()/notify().
  • Возможность сделать блокировку "справедливой". "[fair] locks выступает за предоставление доступа к длинному ожидающему потоку. В противном случае этот замок не гарантирует какого-либо конкретного порядка доступа". Синхронизированные блоки являются несправедливыми.
  • Возможность проверки блокировки блокировки.
  • Возможность получения списка потоков, ожидающих блокировки.

Недостатки реентерабельных замков: -

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

Когда использовать: -

  • ReentrantLock может быть более удобным для использования, если вам нужно реализовать поток, который перемещает связанный список, блокируя следующий node, а затем разблокируя текущий node.
  • Синхронизированное ключевое слово подходит в такой ситуации, как блокировка укрупнения, обеспечивает адаптивное вращение, смещенную блокировку и потенциал для блокировки с помощью анализа эвакуации. Эти оптимизации в настоящее время не реализованы для ReentrantLock.

Подробнее информация.

Ответ 4

Из страницы документации оракула о ReentrantLock:

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

  • A ReentrantLock принадлежит последнему успешному блокированию потока, но еще не отпирает его. Блокировка вызова потока возвращает, успешно приобретая блокировку, когда блокировка не принадлежит другому потоку. Метод немедленно вернется, если текущий поток уже владеет блокировкой.

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

Основные функции ReentrantLock в соответствии с этим статья

  • Возможность блокировки прерывания.
  • Возможность таймаута при ожидании блокировки.
  • Сила для создания честной блокировки.
  • API для получения списка ожидающего потока для блокировки.
  • Гибкость при попытке блокировки без блокировки.

Вы можете использовать ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock, чтобы дополнительно получить контроль над гранулированной блокировкой операций чтения и записи.

Посмотрите на статью статью от Benjamen по использованию разных типов ReentrantLocks

Ответ 5

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

private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy. 

"Политика справедливости" выбирает следующий выполняемый поток для выполнения. Он основан на приоритете, времени с момента последнего запуска, бла-бла

также, Синхронизация может блокироваться бесконечно, если она не может выйти из блока. У Reentrantlock может быть установлен тайм-аут.

Ответ 6

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

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

Ответ 7

Предположим, этот код выполняется в потоке:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

Поскольку поток владеет блокировкой, он разрешит множественные вызовы lock(), поэтому он повторно вводит блокировку. Это может быть достигнуто с помощью счетчика ссылок, поэтому он не должен снова получать блокировку.

Ответ 8

Нужно иметь в виду:

Имя 'ReentrantLock' выдает неверное сообщение о другом механизме блокировки, что они не являются повторно входящими. Это неправда. Блокировка, полученная с помощью "synchronized", также возвращается в Java.

Ключевым отличием является то, что "synchronized" использует внутреннюю блокировку (ту, которая есть у каждого объекта), а блокировка API - нет.