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

317

Я пытаюсь понять, что делает блокировку параллелизма настолько важной, если ее можно использовать 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;
}
adhg
источник
1
Кстати, все внутренние замки Java по своей природе являются реентерабельными.
Аникет Тхакур
@pongapundit, поэтому synchronized(this){synchronized(this){//some code}}не вызовет блокировку. Для внутренней блокировки, если они получают монитор на ресурсе и если они хотят его снова, они могут получить его без мертвой блокировки.
Аникет Тхакур

Ответы:

475

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

private ReentrantLock lock;

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

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

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


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

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


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

Это требование было оспорено, однако; см. следующий комментарий:

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


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

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

oldrinb
источник
26
Ссылка на lycog.com, известная как более масштабируемая, должна быть удалена. В тесте на повторную входящую блокировку каждый раз создается новая блокировка, поэтому исключительная блокировка отсутствует, а полученные данные являются недействительными. Кроме того, ссылка IBM не предлагает исходного кода для базового теста, поэтому невозможно определить, был ли тест даже проведен правильно. Лично я бы просто удалил всю строку о масштабируемости, поскольку вся заявка по существу не поддерживается.
Dev
2
Я изменил пост в свете вашего ответа.
oldrinb
6
Если производительность вызывает у вас большое беспокойство, не забудьте найти способ, где вам вообще не нужна синхронизация.
mcoolive
2
Производительность не имеет смысла для меня вообще. Если бы реентрантная блокировка работала бы лучше, то почему бы не синхронизировать синхронизацию, а не реализовать ее так же, как реантрантную блокировку внутри страны?
Tobi
2
@ user2761895 ReentrantLockPseudoRandomкод в ссылке Lycog использует совершенно новые неконтролируемые блокировки при каждом вызове setSeedиnext
oldrinb
14

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

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

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

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

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

Майк Динеску
источник
чтобы предотвратить возможные мертвые блокировки, потому что кто-то другой, не зная об этом, может попытаться заблокировать ваш объект где-то еще в программе, используйте частный экземпляр объекта в качестве монитора синхронизации, например: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }
sushicutta
9

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

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

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

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

Ключевые функции ReentrantLock согласно этой статье

  1. Возможность блокировки непрерывно.
  2. Возможность тайм-аута в ожидании блокировки.
  3. Сила, чтобы создать справедливую блокировку.
  4. API для получения списка ожидающих потоков для блокировки.
  5. Гибкость, чтобы попытаться заблокировать без блокировки.

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

Взгляните на эту статью Benjamen об использовании различных типов ReentrantLocks

Равиндра Бабу
источник
2

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

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

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

Кроме того, Synchronize может блокировать бесконечно, если не может выйти из блока. Reentrantlock может быть установлен тайм-аут.

j2emanue
источник
1

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

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

Сумит Капур
источник
1

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

private static ReentrantLock lock = new ReentrantLock();

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

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

RonTLV
источник
0

Следует помнить одну вещь:

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

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

Лето
источник
0

Я думаю, что методы wait / notify / notifyAll не принадлежат классу Object, поскольку он загрязняет все объекты методами, которые используются редко. Они имеют больше смысла в специальном классе Lock. Таким образом, с этой точки зрения, возможно, лучше использовать инструмент, который явно предназначен для работы под рукой - то есть ReentrantLock.

Solubris
источник