Using Java semaphores
In this tutorial, we will discuss a simple example of how threads in a concurrent, multi-threaded environment use and compete for shared resources, the problems that might arise due to sharing of these resources and finally, how to solve such a problem using semaphores in Java.
Concurrent access to shared variables
While using multithreading, one might fall into a situation where two or more threads might need access to resources that are shared with other threads. In this case, some problems might arise. As an example, the program below contains two threads, thread1 and thread2. Each thread will increment the static integer counter by a value of 5000.
public class MultithreadingExample { static int counter = 0; public static void incrementCounter(){ counter++; } public static void main(String[] args) { Thread thread1 = new Thread(){ @Override public void run(){ for(int i = 0; i < 5000 ; i++) incrementCounter(); }}; Thread thread2 = new Thread(){ @Override public void run(){ for(int i = 0; i < 5000 ; i++) incrementCounter(); }}; thread1.start(); thread2.start(); while(thread1.isAlive()||thread2.isAlive()){} System.out.println("Counter : "+counter); } }
The code above starts two threads. each thread loops for 5000 iterations, incrementing counter by 1 at each iteration. The main method waits until both threads have terminated by using the "thread.isAlive()" statement. After running the code, we expect each thread to increment counter by 5000, hence counter will have a value of 10000. Lets run the code and analyze the results
run: Counter : 8965 BUILD SUCCESSFUL (total time: 0 seconds)
The problem and the solution
As we can see, the value of counter was not 10000. This is because both threads are accessing counter at the same time, and sometimes one of the threads access the value of counter while the other thread is already assigning it a new value. When this happens, one of the threads overrides the operation of the other, and hence the counter doesn't get incremented 10000 times. To remedy this, we use something called a semaphore. A semaphore can be thought off as a key to a car. One person acquires the key to the car and drives it, while the other person waits. After the first person is done driving, they still have the key, so the second person has to wait until the first person releases the key to be used by somebody else.
The program below is a modification on the first one. The counter access operation is now surrounded by a semaphore.acquire() and a semaphore.release() statements, to make sure that nobody modifies the counter before the holder of the semaphore releases it.
The program below is a modification on the first one. The counter access operation is now surrounded by a semaphore.acquire() and a semaphore.release() statements, to make sure that nobody modifies the counter before the holder of the semaphore releases it.
import java.util.concurrent.Semaphore; import java.util.logging.Level; import java.util.logging.Logger; public class MultithreadingExample { static int counter = 0; static Semaphore semaphore = new Semaphore(1); public static void incrementCounter(){ try { semaphore.acquire(); counter++; semaphore.release(); } catch (InterruptedException ex) { Logger.getLogger(MultithreadingExample.class.getName()).log(Level.SEVERE, null, ex); } } public static void main(String[] args) { Thread thread1 = new Thread() { @Override public void run() { for (int i = 0; i < 5000; i++) { incrementCounter(); } } }; Thread thread2 = new Thread() { @Override public void run() { for (int i = 0; i < 5000; i++) { incrementCounter(); } } }; thread1.start(); thread2.start(); while (thread1.isAlive() || thread2.isAlive()) { } System.out.println("Counter : " + counter); } }
The code above defines a the static semaphore. The constructor takes an integer 1, which defines the number of permits the semaphore can grant. By choosing a value of 1, only one thread can go past the semaphore.acquire() operation, before someone calls the semaphore.release() statement. After running the code about we get the following results:
run:
Counter : 10000
BUILD SUCCESSFUL (total time: 0 seconds)
As we can see, using the semaphores helped organize access to the variable counter, hence no more than one thread can override other thread's operations by using older data. The example discussed here is a simple example. Usually semaphores are used with more complex operations where resources are limited and demand is high, for example, when acquiring access to a shared printer, or changing elements in a shared array in the memory.