Зачем использовать ReentrantLock, если можно использовать синхронизированный (это)?
Я пытаюсь понять, что делает блокировку в параллелизме настолько важной, если можно использовать synchronized (this)
, В коде ниже, я могу сделать либо:
- синхронизировать весь метод или синхронизировать уязвимую область (синхронизирован (это){...})
- ИЛИ заблокируйте уязвимую область кода с помощью 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;
}
6 ответов
ReentrantLock является неструктурированным, в отличие от synchronized
конструкции - то есть вам не нужно использовать блочную структуру для блокировки, и вы даже можете удерживать блокировку между методами. Пример:
private ReentrantLock lock;
public void foo() {
...
lock.lock();
...
}
public void bar() {
...
lock.unlock();
...
}
Такой поток невозможно представить с помощью одного монитора в synchronized
построить.
Помимо этого, ReentrantLock
поддерживает опросы блокировки и прерывания ожидания блокировки, которые поддерживают тайм-аут. ReentrantLock
также имеется поддержка настраиваемой политики справедливости, что позволяет более гибко планировать потоки.
Конструктор для этого класса принимает необязательный параметр справедливости. Когда установлено
true
в условиях разногласий блокировки способствуют предоставлению доступа к дольше ожидающего потока. В противном случае эта блокировка не гарантирует какой-либо конкретный порядок доступа. Программы, использующие справедливые блокировки, к которым обращаются многие потоки, могут показывать более низкую общую пропускную способность (т.е. медленнее, часто намного медленнее), чем программы, использующие настройку по умолчанию, но имеют меньшие отклонения во времени для получения блокировок и гарантируют отсутствие голодания. Однако обратите внимание, что справедливость блокировок не гарантирует справедливость планирования потоков. Таким образом, один из множества потоков, использующих надежную блокировку, может получить ее несколько раз подряд, в то время как другие активные потоки не выполняются и в настоящее время не удерживают блокировку. Также обратите внимание на то, чтоtryLock
Метод не соблюдает настройки честности. Это будет успешно, если блокировка доступна, даже если другие потоки ожидают.
ReentrantLock
такжеможет быть более масштабируемым, выступая гораздо лучше в условиях высокой конкуренции. Вы можете прочитать больше об этом здесь.
Это требование было оспорено, однако; см. следующий комментарий:
В тесте на повторную входную блокировку каждый раз создается новая блокировка, поэтому исключительная блокировка отсутствует, а полученные данные являются недействительными. Кроме того, ссылка IBM не предлагает исходного кода для базового теста, поэтому невозможно определить, был ли тест даже проведен правильно.
Когда вы должны использовать ReentrantLock
s? Согласно этой статье developerWorks...
Ответ довольно прост - используйте его, когда вам действительно нужно что-то
synchronized
не, как и таймерные блокировки, ожидания прерывистой блокировки, блокировки без блочной структуры, множественные переменные условия или опрос блокировки.ReentrantLock
также имеет преимущества масштабируемости, и вы должны использовать его, если на самом деле вы столкнулись с высокой конкуренцией, но помните, что подавляющее большинствоsynchronized
блоки вряд ли когда-либо проявляют спор, не говоря уже о высоком споре. Я бы посоветовал разрабатывать с синхронизацией до тех пор, пока синхронизация не окажется неадекватной, а не просто предполагать, что "производительность будет лучше", если вы используетеReentrantLock
, Помните, что это продвинутые инструменты для опытных пользователей. (И действительно продвинутые пользователи, как правило, предпочитают самые простые инструменты, которые они могут найти, пока они не убедятся, что простые инструменты неадекватны.) Как всегда, сначала сделайте все правильно, а затем подумайте, нужно ли вам делать это быстрее.
ReentrantReadWriteLock
это специализированный замок, тогда как synchronized(this)
это замок общего назначения. Они похожи, но не совсем одинаковы.
Вы правы в том, что вы могли бы использовать synchronized(this)
вместо ReentrantReadWriteLock
но обратное не всегда верно.
Если вы хотите лучше понять, что делает ReentrantReadWriteLock
специальный поиск некоторой информации о синхронизации потока производителя-потребителя.
В общем, вы можете помнить, что синхронизация всего метода и синхронизации общего назначения (используя synchronized
Ключевое слово) можно использовать в большинстве приложений, не слишком задумываясь о семантике синхронизации, но если вам нужно выжать производительность из своего кода, вам может потребоваться изучить другие более тонкие или специальные механизмы синхронизации.
Кстати, используя synchronized(this
) - и вообще блокировка с использованием экземпляра открытого класса - может быть проблематичной, потому что это открывает ваш код для потенциальных мертвых блокировок, потому что кто-то другой, не зная об этом, может попытаться заблокировать ваш объект где-то еще в программе.
Повторная блокировка позволяет владельцу блокировки вводить блоки кода даже после того, как он уже получил блокировку, вводя другие блоки кода. Неповторяющаяся блокировка будет иметь блок держателя блокировки на себе, поскольку она должна будет снять блокировку, полученную из другого блока кода, чтобы повторно получить эту же блокировку для входа во вложенную блокировку, требующую блок кода.
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!
}
Расширенные возможности реентерабельной блокировки включают в себя:
- Возможность иметь более одной условной переменной на монитор. Мониторы, которые используют синхронизированное ключевое слово, могут иметь только одно. Это означает, что входящие блокировки поддерживают более одной очереди wait()/notify().
- Возможность сделать замок "честным". "[справедливые] блокировки предпочитают предоставлять доступ к самому длинному ожидающему потоку. В противном случае эта блокировка не гарантирует какой-либо определенный порядок доступа". Синхронизированные блоки несправедливы.
- Возможность проверить, удерживается ли блокировка.
- Возможность получить список потоков, ожидающих блокировки.
Недостатками реентерабельных замков являются:-
Необходимо добавить заявление на импорт. Необходимо обернуть блокировку приобретений в блок try/finally. Это делает его более уродливым, чем синхронизированное ключевое слово. Ключевое слово synchronized может быть помещено в определения методов, что исключает необходимость в блоке, который уменьшает вложенность.
Когда использовать:-
- ReentrantLock может быть более подходящим для использования, если вам нужно реализовать поток, который пересекает связанный список, блокирует следующий узел и затем разблокирует текущий узел.
- Синхронизированное ключевое слово подходит для таких ситуаций, как укрупнение блокировки, обеспечивает адаптивное вращение, смещенную блокировку и возможность исключения блокировки с помощью анализа выхода. Эти оптимизации в настоящее время не реализованы для ReentrantLock.
Для получения дополнительной информации.
Со страницы документации оракула о ReentrantLock:
Блокировка взаимного исключения с повторным входом с тем же базовым поведением и семантикой, что и для неявной блокировки монитора, доступ к которой осуществляется с помощью синхронизированных методов и операторов, но с расширенными возможностями.
ReentrantLock принадлежит потоку, который последний раз успешно блокировал, но еще не разблокировал его. Поток, вызывающий блокировку, вернется, успешно получив блокировку, когда блокировка не принадлежит другому потоку. Метод вернется немедленно, если текущий поток уже владеет блокировкой.
Конструктор для этого класса принимает необязательный параметр справедливости. Когда установлено значение true, в условиях конкуренции блокировки блокируют доступ к самому длинному ожидающему потоку. В противном случае эта блокировка не гарантирует какой-либо конкретный порядок доступа.
Ключевые функцииReentrantLock согласно этой статье
- Возможность блокировки непрерывно.
- Возможность тайм-аута в ожидании блокировки.
- Сила, чтобы создать справедливую блокировку.
- API для получения списка ожидающих потоков для блокировки.
- Гибкость, чтобы попытаться заблокировать без блокировки.
Вы можете использовать ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock для дальнейшего получения контроля над гранулярной блокировкой при операциях чтения и записи.
Взгляните на эту статью Benjamen об использовании различных типов ReentrantLocks
Синхронизированные блокировки не предлагают никакого механизма ожидания очереди, в котором после выполнения одного потока любой поток, работающий параллельно, может получить блокировку. Из-за этого поток, который находится в системе и работает в течение более длительного периода времени, никогда не получает шанс получить доступ к общему ресурсу, что приводит к голоданию.
Возвратные блокировки очень гибки и имеют политику справедливости, в которой, если поток ожидает более длительное время и после завершения текущего выполняющегося потока, мы можем убедиться, что более длинный ожидающий поток получит шанс доступа к общему ресурсу, тем самым уменьшая пропускная способность системы и делает ее более трудоемкой.
Вы можете использовать повторяющиеся блокировки с политикой справедливости или тайм-аутом, чтобы избежать истощения потоков. Вы можете применить политику справедливости потока. это поможет избежать потока, ожидающего навсегда, чтобы добраться до ваших ресурсов.
private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy.
"Политика справедливости" выбирает следующий выполняемый поток для выполнения. Это основано на приоритете, времени с последнего запуска, бла-бла
Кроме того, Synchronize может блокировать бесконечно, если не может выйти из блока. Reentrantlock может быть установлен тайм-аут.
Нужно иметь в виду:
Имя 'ReentrantLock' выдает неверное сообщение о другом механизме блокировки, что они не являются повторно входящими. Это неправда. Блокировка, полученная с помощью "synchronized", также возвращается в Java.
Ключевым отличием является то, что "synchronized" использует внутреннюю блокировку (ту, которая есть у каждого объекта), а блокировка API - нет.
Я думаю, что методы wait/notify/notifyAll не относятся к классу Object, поскольку они загрязняют все объекты методами, которые используются редко. Они имеют больше смысла в выделенном классе Lock. Так что с этой точки зрения, возможно, лучше использовать инструмент, который специально разработан для работы, например ReentrantLock.
Предположим, этот код выполняется в потоке:
private static ReentrantLock lock = new ReentrantLock();
void accessResource() {
lock.lock();
if( checkSomeCondition() ) {
accessResource();
}
lock.unlock();
}
Поскольку поток владеет блокировкой, он разрешит множественные вызовы lock(), поэтому он повторно вводит блокировку. Это может быть достигнуто с помощью счетчика ссылок, поэтому он не должен снова получать блокировку.