Сравнение Lock и synchronized в Java: преимущества, практика

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Если вам требуется простая блокировка для ограничения одновременного доступа, выберите synchronized. Данный подход идеален для небольших участков кода, где логика блокировки прозрачна и не требует дополнительных настроек:

Java
Скопировать код
synchronized (object) { /* "У меня ключ, вход другим запрещён!" */ }

Однако, при необходимости более сложных механизмов управления блокировками, например, ожидания с таймаутом, не блокирующих попыток для получения блокировки или возможности прерывания потоков, предпочтение следует отдать Lock из пакета java.util.concurrent.locks:

Java
Скопировать код
Lock lock = new ReentrantLock();
if (lock.tryLock()) {
  try { /* "Могу забрать ключ, если меня не прервали и я ещё терпелив!" */ } 
  finally { lock.unlock(); }
}

Таким образом, для рутинных задач подходит Synchronization, в то время как для специальных случаев лучше использовать Lock.

Кинга Идем в IT: пошаговый план для смены профессии

Детальное рассмотрение Lock

Интерфейс Lock предоставляет больше функциональности для управления, чем synchronized. Его преимущества:

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

Используйте более высокий уровень для управления параллелизмом

Используйте утилиты из java.util.concurrent

Практичнее использовать CyclicBarrier или LinkedBlockingQueue из пакета java.util.concurrent вместо Lock, потому что:

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

Будьте осторожны с wait() и notify()

Методы wait() и notify() могут вызвать множество проблем у разработчиков, включая риск взаимных блокировок или пропущенных сигналов. Обратите внимание на более высокоуровневые конструкции, такие как CountDownLatch, Semaphore или Exchanger, для более безопасного и понятного кода.

Почему ReentrantLock может оказаться вашим новым лучшим другом

ReentrantLock может превосходить синхронизированные блоки по производительности при конкурентном доступе множества потоков. Он позволяет:

  • Применять newCondition() для создания нескольких условий ожидания.
  • Использовать сложные техники блокировки, недоступные при использовании synchronized.

Визуализация

Synchronization можно сравнить со старым замком (🔒), защищающим дверь. Ключ уникален, и доступ в помещение можно получить только с его помощью.

Markdown
Скопировать код
Прохождение в комнату:
[🚪 🔒] -> [🔑🚶‍♂️] -> [🚪🔓] -> [🚶‍♂️🔒✨] -> [🚶‍♂️🔒🚪]
(Заперто)    (Владелец ключа)   (Открыто)  (Внутри) (Снова заперто)

Lock, в свою очередь, напоминает систему доступа с электронными картами (🔓💳), что позволяет использовать несколько ключей для доступа к ресурсу.

Markdown
Скопировать код
Прохождение с картой доступа:
[🚪🔓💳] -> [🚶‍♂️💳] -> [🚪🔓✨] 
(Доступ открыт)   (Владелец карты)    (Внутри)
[🚪🔓💳] -> [🚶‍♀️💳] -> [🚪🔓✨]

Проверка доступа производится, и теперь возможен множественный вход.

Надежные стратегии и лучшие практики

Всегда освобождайте блокировку в блоке finally

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

Java
Скопировать код
lock.lock();
try {
    // здесь находится критический участок кода, где важно не потерять ключ
} finally {
    lock.unlock(); // это обязательно для избежания блокировок
}

Используйте очереди и семафоры

Хотя использование Lock может быть полезным, очереди и семафоры обычно упрощают работу с параллельностью. Например, BlockingQueue идеально подходит для сценариев "производитель-потребитель", а Semaphore помогает контролировать доступ к ресурсам.

Простота synchronized

Годы спустя synchronized остаётся достойным инструментом из-за его простоты и надёжности. Его часто выбирают разработчики как первый и самый понятный способ синхронизации.

Полезные материалы

  1. Java Concurrency in Practice — подробное руководство по параллелизму в Java.
  2. Intrinsic Locks and Synchronization — официальное пособие Java по встроенным механизмам блокировок и синхронизации.
  3. Java.util.concurrent.locks — детальная документация на блокировки из пакета java.util.concurrent.
  4. Java concurrency (multi-threading) – Tutorial — захватывающее руководство по многопоточности в Java.
  5. Stack Overflow: Synchronized vs Lock — обсуждение и ответы на актуальные вопросы о синхронизации и блокировках на Stack Overflow.
  6. IBM Developer: Java theory and practice — разъяснение механизмов синхронизации в Java от специалистов IBM Developer.