Семафоры представляют еще одно средство синхронизации для доступа к ресурсу. В 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 выходит из-за стола