Semaphores
A semaphore can be seen as a lock that allows multiple threads to coexist in a certain critical region at any given time. The semaphore uses a counter that determines how many threads can still enter. Once at the semaphore, a thread is allowed to enter only if the number of threads in the critical region is less than the maximum number of threads set when creating the semaphore.
The following operations can be performed on a semaphore:
- acquire - an attempt is made to pass the semaphore; if the number of threads in the critical region is less than the maximum number of threads allowed, the thread can enter.
- release - a thread calls the method to announce that it has finished its work in the critical region, thus allowing an increase in the number of threads that can enter the critical region.
class Something {
static int resource = 0;
}
public class MySemaphore extends Thread {
private int id;
private Semaphore sem;
public MySemaphore(int id, Semaphore sem) {
this.id = id;
this.sem = sem;
}
public static void main(String args[]) {
Semaphore sem = new Semaphore(1);
MySemaphore mt1 = new MySemaphore(0, sem);
MySemaphore mt2 = new MySemaphore(1, sem);
mt1.start();
mt2.start();
try {
mt1.join();
mt2.join();
} catch (InterruptedException ex) {
Logger.getLogger(MySemaphore.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println("count: " + Something.resource);
}
@Override
public void run() {
switch (this.id) {
case 0:
System.out.println("Starting thread " + id);
try {
System.out.println("Thread " + id + " is waiting for a permit.");
sem.acquire();
System.out.println("Thread " + id + " gets a permit.");
for (int i = 0; i < 5; i++) {
Something.resource++;
System.out.println("Thread " + id + ": " + Something.resource);
Thread.sleep(10);
}
} catch (InterruptedException exc) {
System.out.println(exc);
}
System.out.println("Thread " + id + " releases the permit.");
sem.release();
break;
case 1:
System.out.println("Starting thread " + id);
try {
System.out.println("Thread " + id + " is waiting for a permit.");
sem.acquire();
System.out.println("Thread " + id + " gets a permit.");
for (int i = 0; i < 5; i++) {
Something.resource--;
System.out.println("Thread " + id + ": " + Something.resource);
Thread.sleep(10);
}
} catch (InterruptedException exc) {
System.out.println(exc);
}
// Release the permit.
System.out.println("Thread " + id + " releases the permit.");
sem.release();
break;
}
}
public int getThreadId() {
return this.id;
}
}
In the code above, a semaphore is created that allows only one thread in the critical region (notice the form of the Semaphore constructor). Essentially, this semaphore is similar to a lock. The semaphore is created in the main function and passed as a parameter to the thread constructors. Inside the run method, the threads attempt to call acquire, but only one of them will succeed in entering the region after the semaphore. Later, after performing the operations in the critical region, the respective thread announces that it has exited by calling release. Note that, depending on the thread that manages to enter that region, the counter can decrease to -5 and then return to 0, or it can increase to 5 and then reach 0 again.
CheatSheet Semaphore