Семафоры представляют еще одно средство синхронизации для доступа к ресурсу. В Java семафоры представлены классом Semaphore, который располагается в пакете java.util.concurrent.
Для управления доступом к ресурсу семафор использует счетчик, представляющий количество разрешений. Если значение счетчика больше нуля, то поток получает доступ к ресурсу, при этом счетчик уменьшается на единицу. После окончания работы с ресурсом поток освобождает семафор, и счетчик увеличивается на единицу. Если же счетчик равен нулю, то поток блокируется и ждет, пока не получит разрешение от семафора.
Установить количество разрешений для доступа к ресурсу можно с помощью конструкторов класса Semaphore:
Semaphore(int permits) Semaphore(int permits, boolean fair)
Параметр permits
указывает на количество допустимых разрешений для доступа к ресурсу. Параметр fair
во втором конструкторе
позволяет установить очередность получения доступа. Если он равен true
, то разрешения будут предоставляться ожидающим потокам в том
порядке, в каком они запрашивали доступ. Если же он равен false
, то разрешения будут предоставляться в неопределенном порядке.
Для получения разрешения у семафора надо вызвать метод acquire(), который имеет две формы:
void acquire() throws InterruptedException void acquire(int permits) throws InterruptedException
Для получения одного разрешения применяется первый вариант, а для получения нескольких разрешений - второй вариант.
После вызова этого метода пока поток не получит разрешение, он блокируется.
После окончания работы с ресурсом полученное ранее разрешение надо освободить с помощью метода release():
void release() void release(int permits)
Первый вариант метода освобождает одно разрешение, а второй вариант - количество разрешений, указанных в permits.
Используем семафор в простом примере:
import java.util.concurrent.Semaphore; public class Program { public static void main(String[] args) { Semaphore sem = new Semaphore(1); // 1 разрешение CommonResource res = new CommonResource(); new Thread(new CountThread(res, sem, "CountThread 1")).start(); new Thread(new CountThread(res, sem, "CountThread 2")).start(); new Thread(new CountThread(res, sem, "CountThread 3")).start(); } } class CommonResource{ int x=0; } class CountThread implements Runnable{ CommonResource res; Semaphore sem; String name; CountThread(CommonResource res, Semaphore sem, String name){ this.res=res; this.sem=sem; this.name=name; } public void run(){ try{ System.out.println(name + " ожидает разрешение"); sem.acquire(); res.x=1; for (int i = 1; i < 5; i++){ System.out.println(this.name + ": " + res.x); res.x++; Thread.sleep(100); } } catch(InterruptedException e){System.out.println(e.getMessage());} System.out.println(name + " освобождает разрешение"); sem.release(); } }
Итак, здесь есть общий ресурс CommonResource с полем x, которое изменяется каждым потоком. Потоки представлены классом CountThread, который получает семафор и выполняет некоторые действия над ресурсом. В основном классе программы эти потоки запускаются. В итоге мы получим следующий вывод:
CountThread 1 ожидает разрешение CountThread 2 ожидает разрешение CountThread 3 ожидает разрешение CountThread 1: 1 CountThread 1: 2 CountThread 1: 3 CountThread 1: 4 CountThread 1 освобождает разрешение CountThread 3: 1 CountThread 3: 2 CountThread 3: 3 CountThread 3: 4 CountThread 3 освобождает разрешение CountThread 2: 1 CountThread 2: 2 CountThread 2: 3 CountThread 2: 4 CountThread 2 освобождает разрешение
Семафоры отлично подходят для решения задач, где надо ограничивать доступ. Например, классическая задача про обедающих философов. Ее суть: есть несколько философов, допустим, пять, но одновременно за столом могут сидеть не более двух. И надо, чтобы все философы пообедали, но при этом не возникло взаимоблокировки философами друг друга в борьбе за тарелку и вилку:
import java.util.concurrent.Semaphore; public class Program { public static void main(String[] args) { Semaphore sem = new Semaphore(2); for(int i=1;i<6;i++) new Philosopher(sem,i).start(); } } // класс философа class Philosopher extends Thread { Semaphore sem; // семафор. ограничивающий число философов // кол-во приемов пищи int num = 0; // условный номер философа int id; // в качестве параметров конструктора передаем идентификатор философа и семафор Philosopher(Semaphore sem, int id) { this.sem=sem; this.id=id; } public void run() { try { while(num<3)// пока количество приемов пищи не достигнет 3 { //Запрашиваем у семафора разрешение на выполнение sem.acquire(); System.out.println ("Философ " + id+" садится за стол"); // философ ест sleep(500); num++; System.out.println ("Философ " + id+" выходит из-за стола"); sem.release(); // философ гуляет sleep(500); } } catch(InterruptedException e) { System.out.println ("у философа " + id + " проблемы со здоровьем"); } } }
В итоге только два философа смогут одновременно находиться за столом, а другие будут ждать:
Философ 1 садится за стол Философ 3 садится за стол Философ 3 выходит из-за стола Философ 1 выходит из-за стола Философ 2 садится за стол Философ 4 садится за стол Философ 2 выходит из-за стола Философ 4 выходит из-за стола Философ 5 садится за стол Философ 1 садится за стол Философ 1 выходит из-за стола Философ 5 выходит из-за стола Философ 3 садится за стол Философ 2 садится за стол Философ 3 выходит из-за стола Философ 4 садится за стол Философ 2 выходит из-за стола Философ 5 садится за стол Философ 4 выходит из-за стола Философ 5 выходит из-за стола Философ 1 садится за стол Философ 3 садится за стол Философ 1 выходит из-за стола Философ 2 садится за стол Философ 3 выходит из-за стола Философ 5 садится за стол Философ 2 выходит из-за стола Философ 4 садится за стол Философ 5 выходит из-за стола Философ 4 выходит из-за стола