Custom Implementation of CyclicBarrier in Java
In multithreading, sometimes we need multiple threads to wait for each other to reach a common point before continuing execution.
This is where CyclicBarrier comes in handy.
A CyclicBarrier is a synchronization aid that allows a set of threads to wait for each other to reach a common barrier point.
It is part of the java.util.concurrent package.
Let’s create our own version of CyclicBarrier.
Step 1 — Plan the Logic
Our custom CyclicBarrier needs:
- A count of threads to wait for (parties).
- A counter to track threads that have arrived.
- A mechanism to make threads wait (wait() / notifyAll()).
- Barrier reset for reuse.
Step 2 — Create CustomCyclicBarrier Class
public class CustomCyclicBarrier {
private final int parties;
private int waiting = 0;
public CustomCyclicBarrier(int parties) {
this.parties = parties;
}
public synchronized void await() throws InterruptedException {
waiting++;
if (waiting < parties) {
wait(); // thread waits here until all threads arrive
} else {
waiting = 0; // reset for reuse
notifyAll(); // release all waiting threads
}
}
}
Step 3 — Test CustomCyclicBarrier
public class CustomCyclicBarrierTest {
public static void main(String[] args) {
int parties = 3;
CustomCyclicBarrier barrier = new CustomCyclicBarrier(parties);
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " is doing work...");
Thread.sleep((long) (Math.random() * 3000));
System.out.println(Thread.currentThread().getName() + " reached barrier");
barrier.await();
System.out.println(Thread.currentThread().getName() + " passed the barrier");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < parties; i++) {
new Thread(task).start();
}
}
}
Expected Output
Thread-0 is doing work...
Thread-1 is doing work...
Thread-2 is doing work...
Thread-1 reached barrier
Thread-0 reached barrier
Thread-2 reached barrier
Thread-1 passed the barrier
Thread-0 passed the barrier
Thread-2 passed the barrier
Step 4 — Add Barrier Action (Optional)
We can extend our custom barrier to execute a task once all threads arrive.
public class CustomCyclicBarrier {
private final int parties;
private int waiting = 0;
private Runnable barrierAction;
public CustomCyclicBarrier(int parties, Runnable barrierAction) {
this.parties = parties;
this.barrierAction = barrierAction;
}
public synchronized void await() throws InterruptedException {
waiting++;
if (waiting < parties) {
wait();
} else {
if (barrierAction != null) barrierAction.run();
waiting = 0;
notifyAll();
}
}
}
Test with Barrier Action
public class CustomCyclicBarrierTest {
public static void main(String[] args) {
int parties = 3;
Runnable barrierAction = () -> System.out.println("All threads reached the barrier!");
CustomCyclicBarrier barrier = new CustomCyclicBarrier(parties, barrierAction);
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " is doing work...");
Thread.sleep((long) (Math.random() * 3000));
System.out.println(Thread.currentThread().getName() + " reached barrier");
barrier.await();
System.out.println(Thread.currentThread().getName() + " passed the barrier");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < parties; i++) {
new Thread(task).start();
}
}
}
Expected Output:
Thread-0 is doing work...
Thread-1 is doing work...
Thread-2 is doing work...
Thread-1 reached barrier
Thread-0 reached barrier
Thread-2 reached barrier
All threads reached the barrier!
Thread-1 passed the barrier
Thread-0 passed the barrier
Thread-2 passed the barrier
Complete CustomCyclicBarrier Implementation
// CustomCyclicBarrier.java
public class CustomCyclicBarrier {
private final int parties;
private int waiting = 0;
private final Runnable barrierAction;
public CustomCyclicBarrier(int parties) {
this(parties, null);
}
public CustomCyclicBarrier(int parties, Runnable barrierAction) {
this.parties = parties;
this.barrierAction = barrierAction;
}
public synchronized void await() throws InterruptedException {
waiting++;
if (waiting < parties) {
wait(); // thread waits until all threads arrive
} else {
if (barrierAction != null) {
barrierAction.run(); // execute barrier action
}
waiting = 0; // reset barrier for reuse
notifyAll(); // release all waiting threads
}
}
}
Test Code for CustomCyclicBarrier
// CustomCyclicBarrierTest.java
public class CustomCyclicBarrierTest {
public static void main(String[] args) {
int parties = 3;
// Optional barrier action
Runnable barrierAction = () -> System.out.println("All threads reached the barrier!");
CustomCyclicBarrier barrier = new CustomCyclicBarrier(parties, barrierAction);
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " is doing work...");
Thread.sleep((long) (Math.random() * 3000)); // simulate work
System.out.println(Thread.currentThread().getName() + " reached barrier");
barrier.await();
System.out.println(Thread.currentThread().getName() + " passed the barrier");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < parties; i++) {
new Thread(task).start();
}
}
}
Expected Output
Thread-0 is doing work...
Thread-1 is doing work...
Thread-2 is doing work...
Thread-1 reached barrier
Thread-0 reached barrier
Thread-2 reached barrier
All threads reached the barrier!
Thread-1 passed the barrier
Thread-0 passed the barrier
Thread-2 passed the barrier
Advantages of CustomCyclicBarrier
- Helps understand how synchronization works.
- Useful learning exercise for low-level concurrency.
- Can be tailored for specific needs.
Disadvantages
- More error-prone than using Java’s built-in CyclicBarrier.
- Lacks optimizations such as thread fairness and interruption handling.
- No built-in safety against deadlocks.
Real-World Use Case
CustomCyclicBarrier can be used for:
- Simulating teamwork in games or simulations.
- Coordinating tasks in scientific computations.
- Parallel processing with synchronization points.
Conclusion
The custom implementation of CyclicBarrier shows:
- How synchronization works internally.
- How threads can coordinate with low-level locking mechanisms.
- That Java’s built-in CyclicBarrier already handles complexity like reusability, fairness, and barrier actions efficiently.
For production-grade systems, always prefer Java’s built-in CyclicBarrier, but for understanding concurrency deeply, building a custom version is invaluable.
Next Blog- Custom Implementation of CountDownLatch in Java
