Welcome back! In the last lesson, we learned how to manage resource allocation in a restaurant ordering system using semaphores. In this lesson, we’re going to apply similar synchronization principles using CyclicBarrier to simulate a traffic signal system. This lesson will deepen your understanding of task synchronization by coordinating the actions of multiple threads that depend on each other.
In this lesson, we’ll explore:
- How to use CyclicBarrier to coordinate the behavior of multiple threads.
- Implementing a traffic signal system at a four-way intersection.
- How to manage thread execution with ExecutorService and cyclic synchronization.
By the end of this lesson, you’ll know how to apply CyclicBarrier
in real-world scenarios like traffic management, where tasks need to synchronize before proceeding.
You’ve encountered various concurrency tools in previous lessons, such as semaphores for managing limited resources. CyclicBarrier is different—it’s a synchronization aid that makes multiple threads wait for each other before continuing their tasks. This is especially useful when actions must be synchronized, like cars waiting for a green light at an intersection.
In this lesson, we’ll use CyclicBarrier to simulate a traffic signal system. Cars from different directions (threads) arrive at the intersection and must wait for each other before they can proceed. The barrier makes sure that no car proceeds until all have arrived at the intersection, just like how traffic lights coordinate cars at a busy junction.
We begin by defining the core structure for the traffic signal simulation. This includes setting up the CyclicBarrier
and managing threads using ExecutorService
.
Java1import java.util.concurrent.BrokenBarrierException; 2import java.util.concurrent.CyclicBarrier; 3import java.util.concurrent.ExecutorService; 4import java.util.concurrent.Executors; 5import java.util.concurrent.TimeUnit; 6 7public class TrafficSignalSimulation { 8 9 private final CyclicBarrier barrier; 10 private final ExecutorService executor; 11 private final int totalCycles; 12 13 public TrafficSignalSimulation(int numDirections, int totalCycles) { 14 this.barrier = new CyclicBarrier(numDirections, this::changeTrafficLights); 15 this.executor = Executors.newFixedThreadPool(numDirections); 16 this.totalCycles = totalCycles; 17 } 18}
In this snippet:
- CyclicBarrier: This is initialized with the number of directions (cars) needing synchronization. It waits for all cars to arrive at the intersection before allowing any of them to proceed.
- ExecutorService: Manages the threads representing cars arriving from different directions. Each car (thread) moves independently, but is synchronized using the barrier.
- totalCycles: This determines how many times the cars will go through the intersection before the simulation ends.
Java1this.barrier = new CyclicBarrier(numDirections, this::changeTrafficLights);
Here, once all threads reach the barrier, the changeTrafficLights
method is executed. This action simulates the event of traffic lights changing to allow cars to proceed through the intersection.
With this setup, we have the basic structure to simulate a traffic signal system where cars wait for each other at the intersection.
Next, we simulate the movement of cars through the intersection, making them wait for others using the CyclicBarrier
.
Java1public void simulate(int direction) { 2 executor.submit(new Car(direction, barrier, totalCycles)); 3} 4 5private static class Car implements Runnable { 6 private final int direction; 7 private final CyclicBarrier barrier; 8 private final int totalCycles; 9 10 public Car(int direction, CyclicBarrier barrier, int totalCycles) { 11 this.direction = direction; 12 this.barrier = barrier; 13 this.totalCycles = totalCycles; 14 } 15 16 @Override 17 public void run() { 18 String threadName = "Car-" + direction; 19 for (int cycle = 1; cycle <= totalCycles; cycle++) { 20 try { 21 System.out.println(threadName + " arrives at the intersection (Cycle " + cycle + ")."); 22 barrier.await(); // Cars wait at the red light 23 System.out.println(threadName + " proceeds through the intersection (Cycle " + cycle + ")."); 24 Thread.sleep((long) (Math.random() * 1000 + 500)); // Simulate crossing time 25 } catch (InterruptedException e) { 26 Thread.currentThread().interrupt(); 27 System.err.println(threadName + " was interrupted."); 28 break; 29 } catch (BrokenBarrierException e) { 30 System.err.println(threadName + " encountered a broken barrier."); 31 break; 32 } 33 } 34 System.out.println(threadName + " has completed all cycles."); 35 } 36}
In this block, we simulate car movement at an intersection using the Car
class, which implements Runnable
. Each car is modeled as a separate thread.
-
simulate(int direction): Initializes the simulation for a car from a specific direction, submitting it as a task to the
ExecutorService
to run in a separate thread. -
Car Class:
-
Fields:
direction
: Identifies the car's arriving direction.barrier
: TheCyclicBarrier
instance used for synchronization among car threads.totalCycles
: The number of simulation cycles for each car at the intersection.
-
run() Method:
- Cycle Loop: The car announces its arrival for each cycle.
- barrier.await(): The car waits synchronously with others. The barrier ensures all cars wait before proceeding, akin to a red light.
- Thread.sleep(): Simulates the time taken to cross the intersection, introducing a pause.
- Exception Handling:
- InterruptedException: Handles thread interruptions by setting the interrupt status and breaking the loop.
- BrokenBarrierException: Indicates a disrupted barrier; prints an error and exits the loop.
- Completion Message: Prints a message when the car finishes all cycles.
-
This block uses CyclicBarrier
for synchronized starts, ensuring all cars proceed together, similar to traffic light operations at intersections. This structure ensures that no car proceeds through the intersection until all have arrived, mimicking real-life traffic lights.
Now, let’s define the action that happens when all cars have arrived at the intersection and the barrier breaks—changing the traffic lights.
Java1private void changeTrafficLights() { 2 System.out.println("\n=== Traffic lights have changed. Cars can now proceed. ===\n"); 3}
This method is automatically invoked when the barrier is broken (i.e., when all cars are ready to proceed). It mimics the changing of traffic lights, signaling that it’s safe for the cars to continue.
Finally, after all the cars have completed their cycles, we need to shut down the simulation properly.
Java1public void shutdown() { 2 executor.shutdown(); 3 try { 4 if (!executor.awaitTermination(totalCycles * 10, TimeUnit.SECONDS)) { 5 executor.shutdownNow(); 6 if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { 7 System.err.println("Executor did not terminate."); 8 } 9 } 10 } catch (InterruptedException e) { 11 executor.shutdownNow(); 12 Thread.currentThread().interrupt(); 13 } 14 System.out.println("Traffic signal simulation ended."); 15}
- executor.shutdown(): Ensures that all running tasks (car threads) finish before shutting down the system.
- awaitTermination(): Waits for all threads to complete. If they don’t finish in the allotted time, the executor is forcefully terminated.
This ensures that the simulation doesn't terminate prematurely, and all threads complete their tasks safely.
Simulating a traffic signal system using CyclicBarrier helps you understand task synchronization, which is crucial in many real-world systems. Here’s why this concept is important:
- Synchronizing Tasks: The barrier ensures that tasks proceed together, preventing any task from getting ahead before the others are ready.
- Real-world Application: This mirrors real-world traffic management systems, where coordination is essential to avoid collisions and ensure safety.
- Concurrency Control: With CyclicBarrier, you can manage tasks that must operate in lockstep, making it useful for scenarios like simulations, parallel computing, or even multiplayer games.
Now that you’ve seen how CyclicBarrier is used to synchronize tasks in a traffic simulation, let's move to the practice section and apply these concepts. You’ll have the chance to reinforce your understanding with similar synchronization challenges!