Синхронизация против блокировки - PullRequest
168 голосов
/ 17 ноября 2010

java.util.concurrent API предоставляет класс с именем Lock, который в основном сериализует элемент управления для доступа к критическому ресурсу.Это дает метод, такой как park() и unpark().

Мы можем делать подобные вещи, если мы можем использовать ключевое слово synchronized и методы wait() и notify() notifyAll().

Мне интересно, какой из них лучше на практике и почему?

Ответы [ 10 ]

171 голосов
/ 17 ноября 2010

Если вы просто блокируете объект, я бы предпочел использовать synchronized

Пример:

Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!

Вы должны явно сделать try{} finally{} везде.

Принимая во внимание, что с синхронизированным, это очень ясно и невозможно ошибиться:

synchronized(myObject) {
    doSomethingNifty();
}

Тем не менее, Lock s может быть более полезным для более сложных вещей, которые вы не можете приобрести и выпустить втакая чистая манера.Честно говоря, я бы предпочел не использовать голые Lock с, во-первых, и просто использовать более сложные средства управления параллелизмом, такие как CyclicBarrier или LinkedBlockingQueue, если они удовлетворяют вашим потребностям.

У меня никогда не было причин использовать wait() или notify(), но могут быть и хорошие.

63 голосов
/ 17 ноября 2010

Мне интересно, какой из них лучше на практике и почему?

Я обнаружил, что Lock и Condition (и другие новые concurrent классы)просто больше инструментов для набора инструментов.Я мог делать почти все, что мне было нужно, со своим старым молотком (ключевое слово synchronized), но в некоторых ситуациях его было неудобно использовать.Некоторые из этих неловких ситуаций стали намного проще, когда я добавил в свой инструментарий больше инструментов: резиновый молоток, молоток с шариковой ручкой, монтировку и несколько ударов по гвоздям. Однако , мой старый молоток все еще видит свою долю использования.

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

===

Я думаю, что JavaDoc хорошо описывает разницу междуLock и synchronized (выделение мое):

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

...

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

Хотя механизм обзора для синхронизированных методов и операторов значительно упрощает программирование с блокировками монитора и помогает избегайте многих распространенных ошибок программирования, связанных с блокировками, в некоторых случаях вынужно работать с замками более гибко.Например, ** некоторые алгоритмы * для обхода одновременно работающих структур данных требуют использования «передачи с руки» или «блокировки цепи» : выполучить блокировку узла A, затем узла B, затем освободить A и получить C, затем освободить B и получить D и так далее.Реализации интерфейса Lock позволяют использовать такие методы с помощью , позволяющие захватывать и снимать блокировку в различных областях и , позволяющий приобретать и снимать несколько замков в любом порядке .

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

...

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

Реализации блокировки предоставляют дополнительные функциональные возможности по сравнению с использованием синхронизированных методов и операторов, предоставляя неблокирующую попытку получения alock (tryLock ()), попытка получить блокировку, которая может быть прервана (lockInterruptibly (), и попытка получить блокировку, которая может истечь (tryLock (long, TimeUnit)).

...

20 голосов
/ 17 ноября 2010

Вы можете достичь всего, что утилиты в java.util.concurrent делают с низкоуровневыми примитивами, такими как synchronized, volatile или wait / notify

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

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

Сам класс Lock находится на нижнем уровне этой панели инструментов, вам может даже не потребоваться использовать его напрямую (вы можете использовать Queues и Семафор и большую часть времени).

13 голосов
/ 17 ноября 2010

Существует 4 основных фактора, по которым вы хотите использовать synchronized или java.util.concurrent.Lock.

Примечание. Синхронизированная блокировка - это то, что я имею в виду, когда говорю внутреннюю блокировку.

  1. Когда в Java 5 появились ReentrantLocks, оказалось, что они имеют довольно заметную разницу в пропускной способности, чем встроенная блокировка.Если вы ищете более быстрый механизм блокировки и используете 1.5, рассмотрите jucReentrantLock.Собственная блокировка Java 6 теперь сопоставима.

  2. У jucLock есть разные механизмы блокировки.Блокировка прерываемая - попытка блокировки до тех пор, пока блокирующая нить не будет прервана;временная блокировка - попытка заблокировать на определенное время и сдаться, если у вас ничего не получится;tryLock - попытка блокировки, если какой-то другой поток удерживает блокировку, сдаться.Это все включено помимо простого замка.Внутренняя блокировка предлагает только простую блокировку

  3. Стиль.Если и 1, и 2 не попадают в категории того, что вас волнует, большинство людей, включая меня, находят внутреннюю блокирующую семенатику более легкой для чтения и менее многословной, чем блокировка jucLock.
  4. Множественные условия.Объект, на который вы блокируете, может быть уведомлен и ожидать только одного случая.Метод newCondition Локка позволяет одному замку иметь множество причин для ожидания или подачи сигнала.Мне на самом деле пока не нужна эта функциональность на практике, но она полезна для тех, кто в ней нуждается.
5 голосов
/ 14 декабря 2015

Я хотел бы добавить еще несколько вещей поверх Bert F answer.

Locks поддерживают различные методы для более точного управления блокировками, которые более выразительны, чем неявные мониторы (synchronized блокировки)

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

Преимущества Блокировка синхронизации из документации страница

  1. Использование синхронизированных методов или операторов обеспечивает доступ к неявной блокировке монитора, связанной с каждым объектом, но заставляет все получение и снятие блокировки происходить блочно-структурированным способом

  2. Реализации блокировки предоставляют дополнительные функциональные возможности по сравнению с использованием синхронизированных методов и операторов, обеспечивая неблокирующую попытку получения lock (tryLock()), попытку получить блокировку, которая может быть прервана (lockInterruptibly(), и попытка получить замок, который может timeout (tryLock(long, TimeUnit)).

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

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

ReentrantLock ключевые особенности согласно этой статье

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

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

Помимо этих трех ReentrantLocks, Java 8 предоставляет еще один замок

StampedLock:

Java 8 поставляется с новым типом блокировки, называемым StampedLock, который также поддерживает блокировки чтения и записи, как в примере выше. В отличие от ReadWriteLock, методы блокировки StampedLock возвращают штамп, представленный длинным значением.

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

Ознакомьтесь с этой статьей об использовании различного типа замков ReentrantLock и StampedLock.

4 голосов
/ 23 сентября 2011

Основным отличием является справедливость, другими словами, запросы обрабатываются FIFO или может быть завал? Синхронизация на уровне метода обеспечивает справедливое или FIFO-распределение блокировки. Использование

synchronized(foo) {
}

или

lock.acquire(); .....lock.release();

не гарантирует справедливости.

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

См. Книгу Брайана Гетца «Параллелизм Java на практике», раздел 13.3, для полного обсуждения этой темы.

3 голосов
/ 23 марта 2012

Книга Брайана Гетца «Параллелизм Java на практике», раздел 13.3: «... Как и по умолчанию ReentrantLock, встроенная блокировка не дает никаких детерминированных гарантий справедливости, но Статистические гарантии справедливости большинства реализаций блокировки достаточно хороши практически для всех ситуаций ... "

1 голос
/ 16 июня 2016

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

  1. Блокировка одним методом и снятие блокировки другим методом.
  2. У вас есть два потока, работающие над двумя разными частями кода, однако первый поток имеет зависимость от второго потока, чтобы завершить определенный фрагмент кода, прежде чем он продолжит работу (в то время как некоторые другие потоки также работают одновременно). Совместная блокировка может решить эту проблему довольно легко.
  3. Реализация мониторов. Например, простая очередь, в которой методы put и get выполняются из множества разных потоков. Однако вы не хотите перекрывать ни те же методы, что и другие, ни методы put и get. В таком случае закрытый замок очень облегчает жизнь.

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

0 голосов
/ 27 июля 2018

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

void randomFunction(){
.
.
.
synchronize(this){
//do some functionality
}

.
.
.
synchronize(this)
{
// do some functionality
}


} // end of randomFunction

В приведенном выше случае, если поток входит в блок синхронизации, другой блок также блокируется. Если на одном объекте несколько таких блоков синхронизации, все блоки заблокированы. В таких ситуациях java.util.concurrent.Lock может использоваться для предотвращения нежелательной блокировки блоков

0 голосов
/ 30 августа 2017

Большая разница между блокировкой и синхронизацией:

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