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

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

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

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

Ответ 1

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

Пример:

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

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

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

synchronized(myObject) {
    doSomethingNifty();
}

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

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

Ответ 2

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

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

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

===

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

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

...

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

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

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

...

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

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

...

Ответ 3

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

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

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

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

Ответ 4

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

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

  • Когда Java 5 вышла с ReentrantLocks, они оказались довольно заметная пропускная способность разница, а затем внутренняя блокировка. Если вы ищете более быструю блокировку механизма и работают 1,5 рассмотрим j.u.c.ReentrantLock. Ява 6 встроенных блокировок теперь сопоставимы.

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

  • Style. Если и 1, и 2 не падают   в категории того, что вы   касающейся большинства людей,   включая меня, найдут   Сложная сцинтиграфия   читать и менее многословно   j.u.c. Блокировка блокировки.
  • Несколько условий. Объект, который вы lock on может быть уведомлен только и ждал один случай. Замки Метод newCondition позволяет один замок, чтобы иметь множественные причины ждать или сигнализировать. Мне еще предстоит действительно нужна эта функциональность в практике, но это хорошая функция для тем, кто в ней нуждается.

Ответ 5

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

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

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

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

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

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

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

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

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

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

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

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

StampedLock:

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

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

Посмотрите на статью об использовании разных типов блокировок ReentrantLock и StampedLock.

Ответ 6

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

synchronized(foo) {
}

или же

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

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

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

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

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

Ответ 10

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

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

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


} // end of randomFunction

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