Imagine a busy intersection. For a moment, let us get rid of the traffic lights. What’s the result? Blocked intersection, cars slamming into each other, and everyone endlessly honking at each other! Traffic light helped achieve coordination without which everything came down crumbling.
Now replace cars with threads, intersection with shared data, and traffic light with synchronizers. Without synchronizers, things become messy. Infinite loops, inconsistent and incorrect results!
Synchronizers are the tools that helps us achieve this synchronization among threads.
There are several types of synchronizers available in Java that we will be exploring in this article.
Semaphores
In real life, semaphore is a system of visual communication using flags, lights, or some other mechanism. In computer world, semaphore is a data structure that is used to solve variety of synchronization problems.
A semaphore is like an integer with the following properties:
- A semaphore is initialised with an integer value. Once initialised, the value of semaphore cannot be queried.
- A thread can either increment or decrement the value of a semaphore by one.
- If decrementing the value of semaphore results in a negative value, the current thread waits for some other thread to increment the semaphore’s value.
- While incrementing the value of a semaphore if there are threads waiting on the semaphore, any one of them is unblocked.
Java provides Semaphore class under the package java.lang.Object
. While the Semaphore provided in Java has a lot more methods, we would mostly be needing only two, those are
- acquire() ~ Decrement the value of semaphore by 1 and waits if the value of the semaphore is negative.
- release() ~ Increment the value of semaphore by 1 and unblocks a thread from set of threads waiting.
Semaphores at first sight seems very restrictive and non-useful. But, there are some reasons why semaphores exists (and why we are reading them)
- Semaphores deliberately provides minimal interface so that the possibility of error is minimised
- Synchronization solutions implemented using semaphores are usually easy to understand and follow
- Semaphores are easy to implement and are efficient on many systems
Latch
In real life, latch is a device that is used for keeping door or gate closed. While the latch is on, door is closed and no one can pass through it. Once the latch is off, door is open and anyone can pass through it.
In computer world, latch is a data structure that performs a similar function. While it is on, no thread can pass it. But once it is turned off, all threads either already waiting or arriving in future would pass through!
A latch is a data structure with following properties:
- A latch is initialised with a non-negative integer. Latch is considered open with its value become zero.
- Latch exposes a method for threads to block until the latch is opened.
- Another methods allows for decrementing the value of the latch. If the value reaches zero, all threads waiting on latch are unblocked.
Java provides a CountDownLatch class under the java.lang.Object
package. An object of this class is initialised with an integer value. Once initialised, following methods are exposed from this object
- await() ~ Causes thread to block if latch’s value is greater than zero. If latch’s value is 0, threads do not block.
- countDown() ~ Decreases the value of latch by one. If value reaches zero, all the blocked threads are unblocked.
Barrier
Barrier is another data structure that is very useful when crafting solutions to synchronization problems. A barrier for a group of threads ensures that all threads have reached the barrier before they can proceed further.
A barrier is a data structure with the following properties
- It is initialised with a given number of threads n. Once initialised, this number cannot be changed.
- Threads can wait on barrier. A thread waiting on barrier will block until n threads arrive at the barrier.
- When the nth thread arrives, the barrier is tripped and all threads now unblock. The barrier resets after this operation.
- Extra logic can be executed when the barrier trips. This execution is performed in the last thread entering the barrier.
CyclicBarrier class in Java’s
java.lang.Object
package provides implementation for barrier.
Following are methods of interest:
- await() ~ Waits until all parties have invoked await on this barrier.
- CyclicBarrier(int numberOfParties, Runnable barrierAction)
- CyclicBarrier(int numberOfParties)
Using these basic synchronizers, a number of synchronization problems can be solved. Despite their simple interfaces, synchronizer classes are extremely powerful if used properly.
Improper use of these synchronizers can also lead to deadlocks and starvation. Designers should lay extra focus on these details when designing solutions!
I hope you learned something new today. If you enjoyed reading this article, consider subscribing for more such software engineering blogs!
Top comments (0)