Worker Pools in Java

In this example, we’ll look at how to implement a worker pool using threads and queues.

import java.util.concurrent.*;

public class WorkerPool {
    
    // Here's the worker, of which we'll run several concurrent instances.
    // These workers will receive work from the `jobs` queue and send the
    // corresponding results to the `results` queue. We'll sleep a second
    // per job to simulate an expensive task.
    static class Worker implements Runnable {
        private int id;
        private BlockingQueue<Integer> jobs;
        private BlockingQueue<Integer> results;

        Worker(int id, BlockingQueue<Integer> jobs, BlockingQueue<Integer> results) {
            this.id = id;
            this.jobs = jobs;
            this.results = results;
        }

        public void run() {
            while (true) {
                try {
                    Integer j = jobs.take();
                    System.out.println("worker " + id + " started  job " + j);
                    Thread.sleep(1000); // Sleep for a second
                    System.out.println("worker " + id + " finished job " + j);
                    results.put(j * 2);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // In order to use our pool of workers we need to send them work
        // and collect their results. We create 2 queues for this.
        final int numJobs = 5;
        BlockingQueue<Integer> jobs = new LinkedBlockingQueue<>();
        BlockingQueue<Integer> results = new LinkedBlockingQueue<>();

        // This starts up 3 workers, initially blocked because there are no jobs yet.
        ExecutorService executor = Executors.newFixedThreadPool(3);
        for (int w = 1; w <= 3; w++) {
            executor.submit(new Worker(w, jobs, results));
        }

        // Here we send 5 `jobs` and then `close` the jobs channel to
        // indicate that's all the work we have.
        for (int j = 1; j <= numJobs; j++) {
            jobs.put(j);
        }
        executor.shutdown();

        // Finally we collect all the results of the work.
        // This also ensures that the worker threads have finished.
        for (int a = 1; a <= numJobs; a++) {
            results.take();
        }
    }
}

Our running program shows the 5 jobs being executed by various workers. The program only takes about 2 seconds despite doing about 5 seconds of total work because there are 3 workers operating concurrently.

$ java WorkerPool
worker 1 started  job 1
worker 2 started  job 2
worker 3 started  job 3
worker 1 finished job 1
worker 1 started  job 4
worker 2 finished job 2
worker 2 started  job 5
worker 3 finished job 3
worker 1 finished job 4
worker 2 finished job 5

This Java implementation uses java.util.concurrent package to create a thread pool and blocking queues for communication between threads. The Worker class implements Runnable and continuously takes jobs from the jobs queue, processes them, and puts results in the results queue. The main method sets up the worker pool, submits jobs, and collects results.