Java Concurrency and Executors

Last update: 2021-10-21 19:20:35 +0800 +0800
tags: Concurrency | Java | Jdk
Available translations: 日本語 / 中文(简体) / 中文(繁體)

Executor separates submitting a Runnable from deciding where it runs. ExecutorService adds lifecycle management and submit; Callable<V> can return a value or throw a checked exception, and Future<V> represents the eventual result. Future.get() establishes the required visibility of actions performed by the task before completion.

java
Future<Result> future = pool.submit(() -> compute(input));
try {
    return future.get();
} catch (ExecutionException e) {
    throw new ServiceException(e.getCause());
}

Use synchronized, Lock, atomics, concurrent collections, or message passing to establish happens-before relationships. An executor supplies scheduling, not automatic safety for mutable shared objects.

AQS and Synchronizers #

AbstractQueuedSynchronizer (AQS) is a framework for locks, semaphores, latches, and related synchronizers. A subclass maintains an atomic integer state and implements acquire/release attempts. On contention, AQS enqueues waiting threads in a FIFO-style synchronization queue and parks them with LockSupport; release unparks eligible successors. Conditions use associated condition queues and transfer waiters back to the synchronization queue when signalled. Queue order is not, by itself, a universal fairness guarantee.

ThreadPoolExecutor Admission #

ThreadPoolExecutor first creates workers up to corePoolSize; it then queues tasks; only when the queue rejects a task does it grow toward maximumPoolSize; after that it rejects. Consequently an unbounded LinkedBlockingQueue effectively prevents growth beyond core threads, while a SynchronousQueue creates workers aggressively until the maximum. Idle workers above the core size terminate after keepAliveTime; core threads may also time out when explicitly enabled.

Choose a bounded queue and size it from workload evidence, memory limits, and the downstream system’s capacity. The worker factory controls names, daemon status, and uncaught-exception handling. AbortPolicy throws, CallerRunsPolicy applies backpressure by running on the submitter, DiscardPolicy drops work, and DiscardOldestPolicy removes queued work before retrying. Silent dropping is acceptable only when callers can tolerate it.

Lifecycle, Cancellation, and Visibility #

cancel(true) marks a Future cancelled and requests interruption if the task is running; interruption is cooperative, so blocking operations must propagate or restore the interrupted status and CPU-bound tasks must poll it. Cancellation does not roll back side effects.

shutdown() rejects new tasks while allowing queued tasks to finish. shutdownNow() attempts to interrupt workers and returns tasks never begun. Call awaitTermination at application boundaries. Executors factory methods provide fixed, single-thread, scheduled, work-stealing, and virtual-thread executors in current JDKs, but explicit ThreadPoolExecutor construction makes queueing and rejection policy reviewable. Always close an ExecutorService when its lifetime is lexical.

References #

Comments

Read or comment on GitHub (sign-in required to comment)