Task Queues in Java: ExecutorService and ThreadPoolExecutor

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Task Queues in Java: ExecutorService and ThreadPoolExecutor



In multithreaded Java applications, managing tasks and their execution is one of the key challenges. Instead of creating a new thread for each task (which is expensive and inefficient), developers use task queues in combination with thread pools. This allows controlling the number of simultaneously running threads, reusing them, and smoothly handling peak loads.



The foundation for working with task queues in Java is the java.util.concurrent framework, introduced in Java 5. The main components are: ExecutorService, ThreadPoolExecutor, and various implementations of BlockingQueue.



1. ExecutorService — High-Level Interface



ExecutorService is an interface that provides methods for asynchronous task execution. It hides the details of thread management. The most popular way to obtain an instance is to use the factory methods of the Executors class:



ExecutorService executor = Executors.newFixedThreadPool(4); // pool of 4 threadsexecutor.submit(() -> {    System.out.println("Task is running in thread: " + Thread.currentThread().getName());});executor.shutdown(); // shutdown after all tasks complete


However, factory methods have limited configuration capabilities. For fine-tuning, ThreadPoolExecutor is used directly.



2. ThreadPoolExecutor — Configurable Thread Pool



ThreadPoolExecutor is the core of the framework. It accepts a task queue, saturation policy, thread lifetime, and other parameters. The full constructor:



public ThreadPoolExecutor(    int corePoolSize,      // minimum number of threads    int maximumPoolSize,   // maximum number of threads    long keepAliveTime,    // thread idle time before removal    TimeUnit unit,         // time unit    BlockingQueue<Runnable> workQueue, // task queue    RejectedExecutionHandler handler   // policy for overflow);


Example of creating a pool with a custom queue:



BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);ThreadPoolExecutor executor = new ThreadPoolExecutor(    2, 4,    30, TimeUnit.SECONDS,    queue,    new ThreadPoolExecutor.AbortPolicy() // default — throws exception);executor.execute(() -> {    // heavy task});executor.shutdown();


3. BlockingQueue — The Foundation of Task Queues



The type of queue determines how tasks will be stored and passed to threads. The most popular implementations:

  • LinkedBlockingQueue — an unbounded queue based on a linked list. If capacity is not set, it can lead to memory overflow.
  • ArrayBlockingQueue — a bounded queue based on an array. Allows controlling the number of waiting tasks.
  • SynchronousQueue — a zero-capacity queue. Each task is passed directly to a thread, no waiting in the queue.
  • PriorityBlockingQueue — a priority queue. Tasks are sorted by priority (implement Comparable).


Example with a bounded queue and rejection policy:



BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);ThreadPoolExecutor executor = new ThreadPoolExecutor(    2, 5,    10, TimeUnit.SECONDS,    queue,    new ThreadPoolExecutor.CallerRunsPolicy() // task runs in the calling thread);for (int i = 0; i < 20; i++) {    executor.submit(() -> {        Thread.sleep(100);        return Math.random();    });}executor.shutdown();


4. Saturation Policies (RejectedExecutionHandler)



When the queue is over

Recommendations