Java Semaphore против ReentrantLock

Узнайте, что такое семафор и повторный входной замок, а также практические примеры. Мы также рассмотрим некоторые основные различия между ними и варианты использования, в которых мы можем использовать их при работе в многопоточном приложении.

1. Что такое семафор?

Семафор — это конструкция синхронизации потоков, которая действует как блокировка с функциональностью счетчика. Класс семафора, представленный в пакете java.util.concurrent, реализует интерфейс Serializable и присутствует с версии Java 1.5.

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

  • Semaphore.acquire() получает от него разрешение и уменьшает значение счетчика на единицу.
  • Метод Semaphone.release() возвращает разрешение обратно в Semaphore и увеличивает значение счетчика на единицу.

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

В Java присутствуют различные типы семафоров, например:

  • Двоичный семафор
  • Подсчет Семафора
  • Семафор с синхронизацией
  • Ограниченный семафор

Из них Binary Semaphore используется чаще всего в повседневном кодировании. Двоичный семафор может иметь значение счетчика 0 или 1. Это означает, что binary Semaphore защищает доступ к одному общему ресурсу и обеспечивает взаимное исключение, которое позволяет только одному потоку получать доступ к критическому ресурсу за раз.

Давайте теперь рассмотрим пример создания двоичного семафора и то, как мы можем использовать его методы.

Semaphore binarySemaphore = new Semaphore(1);// Acquiring semaphorebinarySemaphore.acquire();// Printing Available PermitsSystem.out.println(binarySemaphore.availablePermits()) // 0// Releasing semaphorebinarySemaphore.release();// Printing Available PermitsSystem.out.println(binarySemaphore.availablePermits()) // 1

Давайте разберемся с помощью псевдокода, как использовать семафор.

class X {private final Semaphore semaphore = new Semaphore(1);// ...public void m() {try {semaphore.acquire();// ... method body} finally {semaphore.release();}}}

2. Что такое ReentrantLock?

ReentrantLockреализует интерфейс Lock и работает почти аналогично ключевому слову synchronized, но с расширенными возможностями.

Реентерабельность означает, что поток может получить одну и ту же блокировку несколько раз без каких-либо проблем.

  • Внутренне Reentrant Lock увеличивает счетчик потоков всякий раз, когда поток вызывает метод lock().
  • Он уменьшает одно и то же значение счетчика всякий раз, когда поток вызывает метод unlock().

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

ReentrantLock reentrantLock = new ReentrantLock();reentrantLock.lock(); // counter=1reentrantLock.lock(); //counter=2// Check if the object is lockedSystem.out.println(reentrantLock.isLocked()); // true// Check if the lock acquired by current ThreadSystem.out.println(reentrantLock.isHeldByCurrentThread()); // true// Release the acquired lock using unlock()reentrantLock.unlock(); //counter=1System.out.println(reentrantLock.isLocked()); // trueSystem.out.println(reentrantLock.getHoldCount()); // 1// Release the acquired lock againreentrantLock.unlock(); //counter=0System.out.println(reentrantLock.isLocked()); // falseSystem.out.println(reentrantLock.getHoldCount()); // 0

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

class X {private final ReentrantLock lock = new ReentrantLock();// ...public void m() {lock.lock(); // block until condition holdstry {// ... method body} finally {lock.unlock();}}}

3. Разница между семафором и ReentrantLock

Давайте теперь рассмотрим основные различия между этими двумя классами.

3.1. Природа возврата

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

С другой стороны, повторная блокировка по своей природе является повторной и позволяет потоку блокировать определенный ресурс несколько раз с помощью метода lock().

3.2 Механизм синхронизации

Семафоры хорошо подходят для передачи сигналов(механизм сигнализации), где потоки используют методы acquire() и release() для обозначения начала и окончания доступа к критическому ресурсу.

Повторно входимая блокировка использует механизм блокировки для блокировки определенного ресурса с помощью метода lock() и после выполнения определенной операции над этим ресурсом снимает блокировку с помощью метода unlock().

3.3 Восстановление после взаимоблокировки

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

Восстановление из тупика немного затруднено в случае повторной блокировки, поскольку она использует владение потоком для ресурса, физически блокируя его, и только поток-владелец может разблокировать этот ресурс. В случае, если поток-владелец входит в состояние бесконечного ожидания или сна, невозможно снять блокировку этого конкретного ресурса, что приводит к ситуации тупика.

3.4 Выброс IllegalMonitorStateException

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

В повторных блокировках поток становится владельцем критического общего ресурса, вызывая метод lock() для ресурса, и в случае, если какой-либо другой поток вызывает метод unlock() для этого ресурса, не владея его блокировкой, то возникнет исключение IllegalMonitorStateException.

3.5 Модификация

Любой поток может использовать методы acquire() и release() семафора для изменения доступных в нем разрешений.

Только текущий поток-владелец, владеющий ресурсом с помощью метода lock(), может изменять ReentrantLock, и никаким другим потокам это делать не разрешено.

5. Когда использовать?

Семафоры могут использоваться для семантики non-ownership-release, где более одного потока могут войти в критическую секцию, и не будет необходимости в механизме блокировки для блокировки общего ресурса. Семафор, по своей конструкции, слеп к тому, какой поток вызывает методы acquire() и release(), все, что его волнует, это разрешение становится доступным.

Если нам нужно повторное взаимное исключение или простой мьютекс, то ReentrantLock — лучший выбор. Reentrant Lock обеспечивает лучший контроль над механизмом блокировки и позволяет только одному потоку получать доступ к критическому разделу за раз, тем самым обеспечивая синхронизацию и устраняя проблемы несогласованности данных при работе в многопоточном приложении.

6. Заключение

Мы узнали о семафорах и ReentrantLock на примерах. Мы также увидели некоторые из основных различий между ними и как выбирать между ними на основе наших потребностей и требований.

Прокрутить вверх