Files
.circleci
.github
.mvn
abstract-document
abstract-factory
active-object
acyclic-visitor
adapter
aggregator-microservices
ambassador
api-gateway
arrange-act-assert
async-method-invocation
balking
bridge
builder
business-delegate
bytecode
caching
callback
chain-of-responsibility
circuit-breaker
cloud-claim-check-pattern
cloud-static-content-hosting
collection-pipeline
combinator
command
commander
composite
composite-entity
converter
cqrs
dao
data-bus
data-locality
data-mapper
data-transfer-object
decorator
delegation
dependency-injection
dirty-flag
domain-model
double-buffer
double-checked-locking
double-dispatch
eip-aggregator
eip-message-channel
eip-publish-subscribe
eip-splitter
eip-wire-tap
etc
event-aggregator
event-asynchronous
event-driven-architecture
event-queue
event-sourcing
execute-around
extension-objects
facade
factory
factory-kit
factory-method
fanout-fanin
feature-toggle
filterer
fluentinterface
flux
flyweight
front-controller
game-loop
guarded-suspension
half-sync-half-async
hexagonal
intercepting-filter
interpreter
iterator
layers
lazy-loading
leader-election
leader-followers
localization
lockable-object
marker
master-worker-pattern
mediator
memento
model-view-controller
model-view-presenter
model-view-viewmodel
module
monad
monitor
monostate
multiton
mute-idiom
naked-objects
null-object
object-mother
object-pool
observer
page-object
parameter-object
partial-response
pipeline
poison-pill
presentation
priority-queue
private-class-data
producer-consumer
promise
property
prototype
proxy
queue-load-leveling
reactor
reader-writer-lock
registry
repository
resource-acquisition-is-initialization
retry
role-object
saga
separated-interface
servant
serverless
service-layer
service-locator
sharding
singleton
spatial-partition
special-case
specification
state
step-builder
strangler
strategy
subclass-sandbox
table-module
template-method
thread-pool
etc
src
README.md
pom.xml
throttling
tls
tolerant-reader
trampoline
transaction-script
twin
typeobjectpattern
unit-of-work
update-method
value-object
version-number
visitor
.all-contributorsrc
.gitignore
CONTRIBUTING.MD
LICENSE.md
PULL_REQUEST_TEMPLATE.md
README.md
checkstyle-suppressions.xml
gpl-3.0.txt
lgpl-3.0.txt
license-plugin-header-style.xml
lombok.config
mvnw
mvnw.cmd
pom.xml
java-design-patterns/thread-pool

layout, title, folder, permalink, categories, language, tags
layout title folder permalink categories language tags
pattern Thread Pool thread-pool /patterns/thread-pool/ Concurrency en
Performance

Intent

It is often the case that tasks to be executed are short-lived and the number of tasks is large. Creating a new thread for each task would make the system spend more time creating and destroying the threads than executing the actual tasks. Thread Pool solves this problem by reusing existing threads and eliminating the latency of creating new threads.

Explanation

Real world example

We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we establish a thread pool.

In plain words

Thread Pool is a concurrency pattern where threads are allocated once and reused between tasks.

Wikipedia says

In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model, a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks. The number of available threads is tuned to the computing resources available to the program, such as a parallel task queue after completion of execution.

Programmatic Example

Let's first look at our task hierarchy. We have a base class and then concrete CoffeeMakingTask and PotatoPeelingTask.

public abstract class Task {

  private static final AtomicInteger ID_GENERATOR = new AtomicInteger();

  private final int id;
  private final int timeMs;

  public Task(final int timeMs) {
    this.id = ID_GENERATOR.incrementAndGet();
    this.timeMs = timeMs;
  }

  public int getId() {
    return id;
  }

  public int getTimeMs() {
    return timeMs;
  }

  @Override
  public String toString() {
    return String.format("id=%d timeMs=%d", id, timeMs);
  }
}

public class CoffeeMakingTask extends Task {

  private static final int TIME_PER_CUP = 100;

  public CoffeeMakingTask(int numCups) {
    super(numCups * TIME_PER_CUP);
  }

  @Override
  public String toString() {
    return String.format("%s %s", this.getClass().getSimpleName(), super.toString());
  }
}

public class PotatoPeelingTask extends Task {

  private static final int TIME_PER_POTATO = 200;

  public PotatoPeelingTask(int numPotatoes) {
    super(numPotatoes * TIME_PER_POTATO);
  }

  @Override
  public String toString() {
    return String.format("%s %s", this.getClass().getSimpleName(), super.toString());
  }
}

Next we present a runnable Worker class that the thread pool will utilize to handle all the potato peeling and coffee making.

@Slf4j
public class Worker implements Runnable {

  private final Task task;

  public Worker(final Task task) {
    this.task = task;
  }

  @Override
  public void run() {
    LOGGER.info("{} processing {}", Thread.currentThread().getName(), task.toString());
    try {
      Thread.sleep(task.getTimeMs());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

Now we are ready to show the full example in action.

    LOGGER.info("Program started");

    // Create a list of tasks to be executed
    var tasks = List.of(
        new PotatoPeelingTask(3),
        new PotatoPeelingTask(6),
        new CoffeeMakingTask(2),
        new CoffeeMakingTask(6),
        new PotatoPeelingTask(4),
        new CoffeeMakingTask(2),
        new PotatoPeelingTask(4),
        new CoffeeMakingTask(9),
        new PotatoPeelingTask(3),
        new CoffeeMakingTask(2),
        new PotatoPeelingTask(4),
        new CoffeeMakingTask(2),
        new CoffeeMakingTask(7),
        new PotatoPeelingTask(4),
        new PotatoPeelingTask(5));

    // Creates a thread pool that reuses a fixed number of threads operating off a shared
    // unbounded queue. At any point, at most nThreads threads will be active processing
    // tasks. If additional tasks are submitted when all threads are active, they will wait
    // in the queue until a thread is available.
    var executor = Executors.newFixedThreadPool(3);

    // Allocate new worker for each task
    // The worker is executed when a thread becomes
    // available in the thread pool
    tasks.stream().map(Worker::new).forEach(executor::execute);
    // All tasks were executed, now shutdown
    executor.shutdown();
    while (!executor.isTerminated()) {
      Thread.yield();
    }
    LOGGER.info("Program finished");

Class diagram

alt text

Applicability

Use the Thread Pool pattern when

  • You have a large number of short-lived tasks to be executed in parallel

Credits