Chaturmind
LearnDSASystem DesignBlogPremium
Sign inGet started
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML

Company

  • Blog
  • Premium
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Concurrency & Multithreading

Threads & Runnable

  • Introduction to Threads
  • ExecutorService & Thread Pools

Synchronization & Memory Model

  • synchronized and Locks
  • volatile and the Memory Model
  • CompletableFuture

Concurrent Collections

  • ConcurrentHashMap & CopyOnWriteArrayList
Chaturmind
← Java Concurrency & Multithreading

Threads & Runnable

  • Introduction to Threads
  • ExecutorService & Thread Pools

Synchronization & Memory Model

  • synchronized and Locks
  • volatile and the Memory Model
  • CompletableFuture

Concurrent Collections

  • ConcurrentHashMap & CopyOnWriteArrayList
HomeLearnJavaJava ConcurrencyExecutorService & Pools
✓ FreeIntermediate· 13 min read

ExecutorService and Thread Pools

Use ExecutorService, thread pools, and Future to manage concurrent tasks without creating raw threads.

Published February 11, 2025


ExecutorService and Thread Pools

Creating raw threads is expensive. The ExecutorService framework provides a managed pool of threads that can be reused across tasks, preventing the overhead of creating and destroying threads for each task.

Creating Thread Pools

import java.util.concurrent.*;

// Fixed pool: exactly N threads
ExecutorService fixed = Executors.newFixedThreadPool(4);

// Single thread: tasks execute sequentially
ExecutorService single = Executors.newSingleThreadExecutor();

// Cached pool: grows as needed, recycles idle threads after 60s
ExecutorService cached = Executors.newCachedThreadPool();

// Scheduled pool: run tasks with delay or periodically
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(2);

// Java 21+: virtual thread executor
ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor();

Submitting Tasks

ExecutorService pool = Executors.newFixedThreadPool(4);

// submit Runnable (no return value)
pool.execute(() -> System.out.println("fire and forget"));

// submit Callable (returns Future)
Future<Integer> future = pool.submit(() -> {
    Thread.sleep(100);
    return 42;
});

// get() blocks until result is ready
Integer result = future.get();           // blocks indefinitely
Integer result2 = future.get(5, TimeUnit.SECONDS); // timeout

// Cancel a task
future.cancel(true); // true = interrupt if running

Proper Shutdown

// Always shut down pools — otherwise the JVM won't exit!
pool.shutdown(); // stop accepting new tasks; wait for running tasks

try {
    if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
        pool.shutdownNow(); // force stop remaining tasks
    }
} catch (InterruptedException e) {
    pool.shutdownNow();
    Thread.currentThread().interrupt();
}

Submitting Multiple Tasks

List<Callable<String>> tasks = List.of(
    () -> fetchUser("u1"),
    () -> fetchUser("u2"),
    () -> fetchUser("u3")
);

// Execute all and wait
List<Future<String>> futures = pool.invokeAll(tasks);
for (Future<String> f : futures) {
    System.out.println(f.get()); // get each result
}

// Get the first successful result
String first = pool.invokeAny(tasks); // returns first completed

ScheduledExecutorService

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

// Run once after delay
scheduler.schedule(() -> System.out.println("delayed"), 5, TimeUnit.SECONDS);

// Run repeatedly at fixed rate
scheduler.scheduleAtFixedRate(
    () -> sendHeartbeat(),
    0,     // initial delay
    30,    // period
    TimeUnit.SECONDS
);

// Run repeatedly with fixed delay BETWEEN executions
scheduler.scheduleWithFixedDelay(
    () -> pollQueue(),
    0, 10, TimeUnit.SECONDS
);

Custom Thread Pool with ThreadPoolExecutor

ThreadPoolExecutor pool = new ThreadPoolExecutor(
    4,                           // corePoolSize
    16,                          // maximumPoolSize
    60L, TimeUnit.SECONDS,       // keepAliveTime
    new LinkedBlockingQueue<>(1000), // task queue
    new ThreadFactory() {           // custom thread names
        int i = 0;
        public Thread newThread(Runnable r) {
            return new Thread(r, "worker-" + i++);
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy() // rejection policy
);

Rejection policies when queue is full:

  • AbortPolicy (default) — throws RejectedExecutionException
  • CallerRunsPolicy — caller thread runs the task (natural backpressure)
  • DiscardPolicy — silently discards
  • DiscardOldestPolicy — discards oldest waiting task

Interview Tips

  1. Never use Executors.newCachedThreadPool() for long-running tasks — it can spawn thousands of threads under load.
  2. newFixedThreadPool with unbounded queue can cause OOM if tasks are submitted faster than they complete — monitor queue depth.
  3. The recommended approach in Spring Boot: inject @Bean TaskExecutor and let Spring manage lifecycle.

Previous

Introduction to Threads

Next

synchronized and Locks

AI Tutor

Lesson: ExecutorService and Thread Pools

Quick actions

AI responses can be inaccurate. Verify critical information.