Lesson 2
Simulating a Car Manufacturing Line with Phaser and ExecutorService
Simulating a Car Manufacturing Line with Phaser and ExecutorService

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.

What You’ll Learn

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.

Recap: Simulating a Car Manufacturing Line

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.

Creating the Car Manufacturing Line Class

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.

Java
1import 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.

Phase 1: Assembly

The first phase in the manufacturing process is assembly. Each car must complete this phase before moving on to the next.

Java
1 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.

Phase 2: Painting

After assembly is complete, the cars move on to the painting phase. The same synchronization logic applies here.

Java
1 // 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.

Phase 3: Inspection

Finally, after painting, the cars go through the inspection phase, completing the manufacturing process.

Java
1 // 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().

Adding Random Delays Between Phases

To make the simulation more realistic, we introduce random delays between phases using a helper method called sleepRandom.

Java
1 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.

Managing Threads with ExecutorService

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.

Java
1import 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.

Submitting Car Manufacturing Tasks

Now, we submit tasks to the ExecutorService, ensuring that each car is processed in its own thread.

Java
1 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.

Shutting Down the Executor

Finally, we shut down the ExecutorService and wait for all tasks to complete.

Java
1 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 the ExecutorService, 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.
Why It Matters

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!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.