KnpCode
Semaphore is a synchronization aid that can be used for inter-communication among threads or to restrict access to resource. An implementation of Semaphore in Java is available for out of box use and it is part of java.util.concurrent package.
Counting semaphore
The implementation of semaphore readily available in the Java concurrency package is a counting semaphore.
Conceptually, a semaphore maintains a set of permits. When a semaphore is created it is created with a given number of permits. The working of semaphore in Java can be explained using the following steps-
- A thread that wants to access a shared resource tries to acquire a permit using the acquire() method.
- If permit is available or in other words if semaphore count is greater than zero then the thread acquires a permit otherwise thread is blocked.
- With every successful acquisition of the permit the count is also decremented. If count becomes zero then no permit can be given.
- When the thread is done with the shared resource it can release the acquired permit using the release() method. This increments the semaphore’s count.
- Any blocking thread waiting to acquire a permit can get a permit once count is more than zero.
Java semaphore constructors
- Semaphore(int permits)— Creates a Semaphore with the given number of permits and nonfair fairness setting.
- Semaphore(int permits, boolean fair)— Creates a Semaphore with the given number of permits and the given fairness setting.
Semaphore example in Java
Let’s say there is a method that is computation heavy and you want to restrict access to this method to 2 threads at any given time. In this scenario you can use Semaphore created with 2 permits.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class SemaphoreDemo < public static void main(String[] args) < // Semaphore with 2 permits Semaphore s = new Semaphore(2); ExecutorService ex = Executors.newFixedThreadPool(4); // Executing 6 times with a pool of 4 threads for(int i = 0; i < 6; i++) < ex.execute(new HeavyDuty(s)); >ex.shutdown(); > > class HeavyDuty implements Runnable < private Semaphore s; HeavyDuty(Semaphore s)< this.s = s; >@Override public void run() < try < s.acquire(); System.out.println("Permit ACQUIRED by " + Thread.currentThread().getName()); doProcessing(); System.out.println("Permit released by " + Thread.currentThread().getName()); s.release(); >catch (InterruptedException e) < // TODO Auto-generated catch block e.printStackTrace(); >> private void doProcessing() throws InterruptedException < System.out.println("doing heavy computation processing "); Thread.sleep(5000); >>
Permit ACQUIRED by pool-1-thread-1 doing heavy computation processing Permit ACQUIRED by pool-1-thread-2 doing heavy computation processing Permit released by pool-1-thread-1 Permit ACQUIRED by pool-1-thread-4 doing heavy computation processing Permit released by pool-1-thread-2 Permit ACQUIRED by pool-1-thread-3 doing heavy computation processing Permit released by pool-1-thread-4 Permit ACQUIRED by pool-1-thread-1 doing heavy computation processing Permit released by pool-1-thread-3 Permit ACQUIRED by pool-1-thread-2 doing heavy computation processing Permit released by pool-1-thread-1 Permit released by pool-1-thread-2
As you can see at any given time permits are acquired by 2 threads.
Binary Semaphore
A semaphore in Java created with only one permit can serve as a mutual exclusion lock. This is more commonly known as a binary semaphore, because it only has two states: one permit available, or zero permits available.
Binary Semaphore example in Java
Here is a simple binary semaphore example where a shared counter is used among multiple threads. Binary semaphore let only one thread access the shared resource at any given time.
public class SemaphoreDemo < public static void main(String[] args) < // Semaphore with 1 permit Semaphore s = new Semaphore(1); SharedCounter counter = new SharedCounter(s); for(int i = 0; i < 6; i++) < new Thread(counter).start(); >> > class SharedCounter implements Runnable < private int c = 0; private Semaphore s; SharedCounter(Semaphore s)< this.s = s; >@Override public void run() < try < s.acquire(); incrCounter(); s.release(); >catch (InterruptedException e) < // TODO Auto-generated catch block e.printStackTrace(); >> // incrementing the value public void incrCounter() throws InterruptedException < Thread.sleep(10); System.out.println("Value for Thread After increment - " + Thread.currentThread().getName() + " " + ++c); >>
Value for Thread After increment - Thread-0 1 Value for Thread After increment - Thread-1 2 Value for Thread After increment - Thread-2 3 Value for Thread After increment - Thread-3 4 Value for Thread After increment - Thread-4 5 Value for Thread After increment - Thread-5 6
To see how threads can interfere you can comment the acquire and release methods with in the run() method.
public void run() < try < //s.acquire(); incrCounter(); //s.release(); >catch (InterruptedException e) < // TODO Auto-generated catch block e.printStackTrace(); >>
Running after commenting gives the following output for one run where same count is displayed for 2 threads.
Value for Thread After increment - Thread-4 1 Value for Thread After increment - Thread-2 2 Value for Thread After increment - Thread-0 3 Value for Thread After increment - Thread-5 4 Value for Thread After increment - Thread-3 1 Value for Thread After increment - Thread-1 2
Methods in Java Semaphore class
Some of the important methods in Semaphore class in Java are as follows-
- acquire()— Acquires a permit from this semaphore, blocking until one is available, or the thread is interrupted.
- acquire(int permits)— Acquires the given number of permits from this semaphore, blocking until all are available, or the thread is interrupted.
- availablePermits()— Returns the current number of permits available in this semaphore.
- drainPermits()— Acquires and returns all permits that are immediately available, or if negative permits are available, releases them.
- getQueuedThreads()— Returns a collection containing threads that may be waiting to acquire.
- isFair()— Returns true if this semaphore has fairness set true.
- release()— Releases a permit, returning it to the semaphore.
- tryAcquire()— Acquires a permit from this semaphore, only if one is available at the time of invocation.
- tryAcquire(int permits)— Acquires the given number of permits from this semaphore, only if all are available at the time of invocation.
That’s all for the topic Semaphore in Java With Examples. If something is missing or you have something to share about the topic please write a comment.
Semaphore in java with example
Семафоры представляют еще одно средство синхронизации для доступа к ресурсу. В 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(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 выходит из-за стола