Двоичный семафор Java (+Пример)

Двоичный семафор — это примитив синхронизации, который может содержать только два значения, обычно 0 и 1. Мы используем двоичный семафор для управления доступом к общему ресурсу несколькими потоками в параллельной среде.

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

1. Как работает двоичный семафор?

Двоичный семафор можно визуализировать как флаг, который указывает, доступен ресурс или нет. Он имеет только два состояния: 0(недоступен) и 1(доступен).

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

Важно понимать, что значение семафора всегда должно находиться в пределах от 0 до 1.

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

2. Когда использовать двоичный семафор?

Как обсуждалось выше, двоичный семафор может иметь значение 0 или 1. Это означает, что двоичный семафор защищает доступ к ОДНОМУ общему ресурсу, поэтому внутренний счетчик семафора может принимать только значения 1 или 0.

Поэтому всякий раз, когда вам требуется защитить доступ к ОДНОМУ ресурсу, к которому обращаются несколько потоков, вы можете использовать двоичный семафор.

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

Подробнее: Как использовать блокировки в Java

3. Как использовать двоичный семафор?

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

3.1. Печать задания

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

public class PrintingJob implements Runnable {private PrinterQueue printerQueue;public PrintingJob(PrinterQueue printerQueue) {this.printerQueue = printerQueue;}@Overridepublic void run() {System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName());printerQueue.printJob(new Object());}}

3.3.Очередь печати

Этот класс представляет очередь принтера/принтер. Обратите внимание, что мы передаем значение 1 как параметр конструктора этого семафора, поэтому вы создаете двоичный семафор.

public class PrinterQueue {private final Semaphore semaphore;public PrinterQueue(){semaphore = new Semaphore(1);}public void printJob(Object document) {try {semaphore.acquire();Long duration =(long)(Math.random() * 10000);System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " +(duration / 1000) + " seconds :: Time - " + new Date());Thread.sleep(duration);}catch(InterruptedException e) {e.printStackTrace();} finally {System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName());semaphore.release();}}}

3.3.Демо

Давайте проверим нашу программу принтера:

public class SemaphoreExample{public static void main(String[] args){PrinterQueue printerQueue = new PrinterQueue();Thread thread[] = new Thread[10];for(int i = 0; i < 10; i++){thread[i] = new Thread(new PrintingJob(printerQueue), "Thread " + i);}for(int i = 0; i < 10; i++){thread[i].start();}}}

Вывод программы:

Thread 0: Going to print a documentThread 9: Going to print a documentThread 8: Going to print a documentThread 5: Going to print a documentThread 7: Going to print a documentThread 6: Going to print a documentThread 3: Going to print a documentThread 4: Going to print a documentThread 2: Going to print a documentThread 1: Going to print a documentThread 0: PrintQueue: Printing a Job during 3 seconds :: Time - Tue Jan 06 18:00:12 IST 2015Thread 0: The document has been printedThread 9: PrintQueue: Printing a Job during 0 seconds :: Time - Tue Jan 06 18:00:16 IST 2015Thread 9: The document has been printedThread 8: PrintQueue: Printing a Job during 7 seconds :: Time - Tue Jan 06 18:00:16 IST 2015Thread 8: The document has been printedThread 5: PrintQueue: Printing a Job during 0 seconds :: Time - Tue Jan 06 18:00:24 IST 2015Thread 5: The document has been printedThread 7: PrintQueue: Printing a Job during 4 seconds :: Time - Tue Jan 06 18:00:24 IST 2015Thread 7: The document has been printedThread 6: PrintQueue: Printing a Job during 3 seconds :: Time - Tue Jan 06 18:00:29 IST 2015Thread 6: The document has been printedThread 3: PrintQueue: Printing a Job during 8 seconds :: Time - Tue Jan 06 18:00:33 IST 2015Thread 3: The document has been printedThread 4: PrintQueue: Printing a Job during 0 seconds :: Time - Tue Jan 06 18:00:41 IST 2015Thread 4: The document has been printedThread 2: PrintQueue: Printing a Job during 4 seconds :: Time - Tue Jan 06 18:00:42 IST 2015Thread 2: The document has been printedThread 1: PrintQueue: Printing a Job during 3 seconds :: Time - Tue Jan 06 18:00:46 IST 2015Thread 1: The document has been printed

Посмотрите на метод printJob(). Этот метод показывает три шага, которые необходимо выполнить при использовании семафора для реализации критической секции и защиты доступа к общему ресурсу:

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

4. Справедливость семафора

Класс java.util.concurrent.Semaphore принимает второй параметр в своем конструкторе. Этот параметр должен принимать логическое значение.

  • Если вы дадите ему значение false, вы создадите семафор, который будет работать в нечестном режиме. Это поведение по умолчанию.
  • Если вы дадите ему истинное значение, вы создадите семафор, который будет работать в честном режиме.
Semaphore semaphore = new Semaphore(1); // Unfair Semaphore(Default)Semaphore fairSemaphore = new Semaphore(1, true); // Fair Semaphore

Режим справедливости определяет, как потоки получают разрешения, когда они доступны.

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

В честном режиме семафор гарантирует, что потоки получают разрешения в том порядке, в котором они их запросили(FIFO – First In, First Out). Это реализуется путем поддержания очереди ожидающих потоков. Это гарантирует, что ни один поток не будет голодать.

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