it-roy-ru.com

Синхронизация против блокировки

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

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

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

161
daydreamer

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

Пример:

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

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

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

synchronized(myObject) {
    doSomethingNifty();
}

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

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

166
Steven Schlansker

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

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

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

===

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

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

...

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

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

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

...

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

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

...

60
Bert F

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

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

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

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

21
Thilo

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

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

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

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

  3. Стиль. Если оба 1 и 2 не падают на категории того, что вы есть касается большинства людей, включая меня, нашел бы Внутренняя блокировка семенатики проще читать и менее многословно j.u.c.Блокировка блокировки.
  4. Несколько условий. Объект, на котором вы Заблокированы, может быть только уведомлен и Ждал одного случая. Метод Lock NewCondition позволяет Одиночному замку иметь множество причин ждать или сигнализировать. Мне пока не нужно На самом деле эта функциональность нужна в Практике, но это хорошая функция для Тех, кто в ней нуждается.
14
John Vint

Я хотел бы добавить еще кое-что поверх Bert F ответа.

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

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

Преимущества Lock over Synchronization из документации страница

  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.

5
Ravindra babu

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

synchronized(foo) {
}

или же

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

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

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

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

4
Brian Tarbox

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

3
bodrin

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

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

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

1
Md Monjur Ul Hasan

Основное различие между блокировкой и синхронизацией:

  • с замками вы можете снимать и приобретать замки в любом порядке.
  • с синхронизированными вы можете снять блокировки только в том порядке, в котором они были получены.
0
NKumar

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

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

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


} // end of randomFunction

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

0
Shyam