Oftentimes it’s necessary to write an application in which there are multiple threads that are mostly independent from one another, but require a sync up occasionally before proceeding. This is a great use case for a CyclicBarrier
. It is called a Barrier because it prevents threads from proceeding, and it is called Cyclic because once all threads have reached the barrier, they are released and the barrier can then be reused, unlike latches.
Using a Barrier
There is a fixed number of threads that the Barrier will wait for before tripping and cycling over. The term usually used for this number is parties, as in a party of threads.
Using a barrier comes down to 2 steps:
- Create a barrier with a given ‘parties’ number
- Call
await
on that barrier (from a thread)
It’s important to note that all calls to
await
will block until the n’th call. Where n is the number specified when creating the barrier.
For this reason it is important that the number of threads coordinating with barrier is equal to or greater to the party number. If not, all threads will block and your application will never terminate. (At least not gracefully).
A Quick Example
Let’s imagine a scenario. There are group of 4 friends who love traveling. However, each friend will only travel from place to place via their preferred method of travel. In our group, we have a walker, a bus-rider, a train-taker, and a plane-passenger. The plane-passenger always arrives to the destination first, and the walker always gets there last, but they always meet up eventually.
Over the summer they decide to take a trip up the east coast of the United States to Montreal, Canada. During the trip, they will stop at these cities: Miami, Jacksonville, Washington, New York and Albany. To ensure everyone makes it safely, once one of the friends makes it to a city, he/she will wait until the others arrive. Then they will have a beer, sleep for the night, and head out the next day to the next city via their preferred method of travel.
The full code is below and then I’ll explain in more detail:
public class BarrierExample { //Trip up the east coast of the USA public static String[] locations = {"Miami", "Jacksonville", "Washington", "New York", "Albany", "Montreal"}; public static CyclicBarrier barrier; public static void main(String [] args) { Thread walker = new Thread(getRunnable("walker", 5)); Thread bus = new Thread(getRunnable("bus", 4)); Thread train = new Thread(getRunnable("train", 3)); Thread plane = new Thread(getRunnable("plane", 2)); barrier = new CyclicBarrier(4); walker.start(); bus.start(); train.start(); plane.start(); } public static Runnable getRunnable(final String type, final int secs) { return new Runnable() { public void run() { System.out.println("Thread " + type + " starting trip"); for (int i = 0; i < locations.length; i++) { System.out.println("Thread " + type + " leaving for " + locations[i]); try { Thread.sleep(secs * 1000); //simulates time it takes to travel System.out.println("Thread " + type + " reached " + locations[i]); System.out.println("Thread " + type + " waiting for others..."); barrier.await(); } catch(InterruptedException ie) { System.out.println("Thread " + type + " interrupted"); } catch(BrokenBarrierException bbe) { System.out.println("Barrier has been broken!"); } } System.out.println("Thread " + type + " finished trip!"); } }; } }
Defining our Runnables
In this example, each friend is represented as a Runnable
:
public static Runnable getRunnable(final String type, final int secs) {
The type
parameter is mostly used for identification purposes. The secs
parameter is provided to make our threads sleeps for the specified period. This allows us to simulate the time it takes to travel by that medium. In our case, the plane is the fastest (sleeps for 2 seconds), while the walker takes an entire 5 seconds to travel. We also initialize our CyclicBarrier
to 4, which is the number of friends in the group:
Thread walker = new Thread(getRunnable("walker", 5)); Thread bus = new Thread(getRunnable("bus", 4)); Thread train = new Thread(getRunnable("train", 3)); Thread plane = new Thread(getRunnable("plane", 2)); barrier = new CyclicBarrier(4);
Waiting via the Barrier
Our friends need to travel to each location, but can’t proceed to the next city until the entire group arrives. Each thread will need to call await
to ensure they don’t head off to the next city too soon:
Thread.sleep(secs * 1000); //simulates time it takes to travel System.out.println("Thread " + type + " reached " + locations[i]); System.out.println("Thread " + type + " waiting for others..."); barrier.await(); //blocks until others have arrived
The code also allows all of the friends to visit each city using the same object, showcasing the cyclic nature of the barrier.
The CyclicBarrier
can be used in various applications for synchronizing work between threads. For more information, consult the Javadocs