Welcome back! In this unit, we’re diving into a car manufacturing line simulation using two important concurrency utilities: Phaser
and ExecutorService
. This lesson will expand your understanding of multi-stage processing and synchronization in Java by simulating real-world scenarios where tasks must follow specific phases.
In this lesson, you’ll learn:
- How to use
Phaser
to synchronize threads across different stages of a process. - How to manage threads effectively using
ExecutorService
. - How to simulate a car manufacturing line with multiple stages that must coordinate across phases.
By the end of this lesson, you will know how to simulate phased processes using Phaser
and manage threads with ExecutorService
.
In this lesson, we’re simulating a car manufacturing line with three stages: assembly, painting, and inspection. Multiple cars will be processed simultaneously, but each car must complete a phase before any of them move on to the next. This requires synchronization, which is where Phaser
comes in.
Phaser allows us to coordinate threads as they move through each phase, ensuring that no thread moves ahead until all threads are ready to proceed. Meanwhile, ExecutorService helps us manage the thread lifecycle by controlling the submission of tasks and ensuring that the tasks are executed in parallel.
Let’s start by creating the class that simulates the car manufacturing process. The class uses Phaser
to synchronize each phase (assembly, painting, inspection) for multiple cars.
Java1import java.util.concurrent.Phaser; 2 3public class CarManufacturingLine { 4 private final Phaser phaser; 5 6 public CarManufacturingLine(Phaser phaser) { 7 this.phaser = phaser; 8 }
In this class, we pass in a Phaser
instance that will control the synchronization across all cars. Each car will proceed through three phases: assembly, painting, and inspection.
The first phase in the manufacturing process is assembly. Each car must complete this phase before moving on to the next.
Java1 public void manufactureCar(int carId) { 2 String carName = "Car-" + carId; 3 System.out.println(carName + " started manufacturing."); 4 5 // Phase 1: Assembly 6 System.out.println(carName + " is in assembly."); 7 sleepRandom(); 8 phaser.arriveAndAwaitAdvance(); 9 System.out.println(carName + " completed assembly."); 10 }
The arriveAndAwaitAdvance()
method ensures that each thread (representing a car) completes the current phase before moving to the next. This method is crucial for ensuring that all cars finish their assembly phase before moving to the painting phase.
After assembly is complete, the cars move on to the painting phase. The same synchronization logic applies here.
Java1 // Phase 2: Painting 2 System.out.println(carName + " is in painting."); 3 sleepRandom(); 4 phaser.arriveAndAwaitAdvance(); 5 System.out.println(carName + " completed painting."); 6 }
Again, phaser.arriveAndAwaitAdvance()
ensures that all cars wait for each other to finish the painting phase before moving on to inspection.
Finally, after painting, the cars go through the inspection phase, completing the manufacturing process.
Java1 // Phase 3: Inspection 2 System.out.println(carName + " is in inspection."); 3 sleepRandom(); 4 phaser.arriveAndAwaitAdvance(); 5 System.out.println(carName + " completed inspection."); 6 7 System.out.println(carName + " has completed manufacturing."); 8 }
Each car completes its inspection and finishes the manufacturing process, synchronized with all other cars using phaser.arriveAndAwaitAdvance()
.
To make the simulation more realistic, we introduce random delays between phases using a helper method called sleepRandom
.
Java1 private void sleepRandom() { 2 try { 3 Thread.sleep((long) (Math.random() * 2000 + 500)); 4 } catch (InterruptedException e) { 5 Thread.currentThread().interrupt(); 6 System.err.println("Thread was interrupted."); 7 } 8 } 9}
This method simulates the varying time it takes for different manufacturing stages, as would happen in a real car production line.
Next, we need to manage the threads that simulate the cars in the manufacturing line. We use ExecutorService
for this purpose. It allows us to submit multiple car manufacturing tasks and control their execution in parallel.
Java1import java.util.concurrent.ExecutorService; 2import java.util.concurrent.Executors; 3import java.util.concurrent.Phaser; 4import java.util.concurrent.TimeUnit; 5 6public class Main { 7 8 public void start() { 9 int numberOfCars = 3; 10 ExecutorService executor = Executors.newFixedThreadPool(numberOfCars); 11 Phaser phaser = new Phaser(1); // Register main thread
We create a fixed thread pool with newFixedThreadPool(numberOfCars)
, which limits the number of threads that can run concurrently. This simulates the number of car manufacturing lines available.
Now, we submit tasks to the ExecutorService
, ensuring that each car is processed in its own thread.
Java1 for (int i = 1; i <= numberOfCars; i++) { 2 phaser.register(); // Register car thread 3 int carId = i; 4 executor.submit(() -> { 5 try { 6 new CarManufacturingLine(phaser).manufactureCar(carId); 7 } finally { 8 phaser.arriveAndDeregister(); 9 } 10 }); 11 }
We register each car with the phaser before starting its manufacturing process. Once a car finishes its task, it calls arriveAndDeregister()
, which lets the phaser know that this thread has completed its work and will not participate in future phases.
Finally, we shut down the ExecutorService
and wait for all tasks to complete.
Java1 phaser.arriveAndDeregister(); // Deregister main thread 2 3 executor.shutdown(); 4 try { 5 if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { 6 executor.shutdownNow(); 7 if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { 8 System.err.println("Executor did not terminate."); 9 } 10 } 11 } catch (InterruptedException e) { 12 executor.shutdownNow(); 13 Thread.currentThread().interrupt(); 14 } 15 16 System.out.println("All cars have been manufactured."); 17 } 18 19 public static void main(String[] args) { 20 new Main().start(); 21 } 22}
The key methods that are used here are shutdown()
and awaitTermination()
:
shutdown()
initiates an orderly shutdown of theExecutorService
, stopping the submission of new tasks.awaitTermination()
waits for all threads to finish execution within a given time limit. If they don’t,shutdownNow()
forcefully terminates any remaining tasks.
Simulating a car manufacturing line with Phaser
and ExecutorService
showcases how multi-phase workflows can be handled efficiently using Java concurrency utilities. This is especially useful in real-world scenarios where tasks must be synchronized across phases, such as in assembly lines or complex project management.
By leveraging Phaser
, you ensure that all cars in the simulation complete one phase before proceeding to the next. Meanwhile, ExecutorService
effectively manages the parallel execution of tasks, simulating multiple cars being processed concurrently.
Now that you’ve learned how to use Phaser
and ExecutorService
in a car manufacturing simulation, let's move to the practice section. You’ll have the opportunity to build your own multi-phase simulation and tackle similar problems to reinforce your understanding. Let’s get started!