Backend Development 12 min read

Java ThreadPool Creation Methods and Usage Examples

This article explains the seven ways to create Java thread pools, categorizes them by ThreadPoolExecutor and Executors, and provides detailed code examples for fixed-size, cached, scheduled, single‑thread, work‑stealing pools, as well as custom thread factories and task result handling.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Java ThreadPool Creation Methods and Usage Examples

The creation of thread pools in Java has seven methods, which can be divided into two main categories: pools created via ThreadPoolExecutor and pools created via the Executors utility class.

The seven specific creation methods are:

Executors.newFixedThreadPool : creates a pool with a fixed number of threads; excess tasks wait in a queue.

Executors.newCachedThreadPool : creates a pool that reuses idle threads and creates new ones when needed.

Executors.newSingleThreadExecutor : creates a single‑threaded pool that guarantees FIFO execution.

Executors.newScheduledThreadPool : creates a pool that can execute delayed or periodic tasks.

Executors.newSingleThreadScheduledExecutor : a single‑threaded scheduled pool.

Executors.newWorkStealingPool (added in JDK 1.8): creates a work‑stealing pool with nondeterministic task order.

ThreadPoolExecutor : the most configurable way, exposing seven parameters for fine‑tuning.

1. Fixed‑size thread pool

public class ThreadPoolDemo3 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        // Task 1
        threadPool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        // Task 2
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
    }
}
// Output:
// pool-1-thread-1
// pool-1-thread-2

Using submit returns a Future that can hold a result, while execute only runs a task without a return value.

2. Custom thread factory (name & priority)

public class ThreadPoolDemo5 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("MyThread" + r.hashCode());
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        };
        ExecutorService threadPool = Executors.newFixedThreadPool(2, threadFactory);
        Future
result = threadPool.submit(new Callable
() {
            @Override
            public Integer call() throws Exception {
                int num = new Random().nextInt(10);
                System.out.println(Thread.currentThread().getPriority() + ", random: " + num);
                return num;
            }
        });
        System.out.println("Result: " + result.get());
    }
}

The custom factory allows setting thread naming rules, priority, group, and daemon status.

3. Cached thread pool

public class ThreadPoolDemo6 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            service.submit(() -> {
                System.out.println("i : " + finalI + " | thread: " + Thread.currentThread().getName());
            });
        }
    }
}

This pool creates threads as needed and reuses idle ones after a timeout; it is suitable for short‑lived, bursty workloads but may consume many resources.

4. Scheduled tasks

a. One‑time delayed execution

public class ThreadPoolDemo7 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("Add time: " + LocalDateTime.now());
        service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("Run time: " + LocalDateTime.now());
            }
        }, 3, TimeUnit.SECONDS);
    }
}

b. Fixed‑rate execution

public class ThreadPoolDemo8 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("Add time: " + LocalDateTime.now());
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("Run task: " + LocalDateTime.now());
            }
        }, 2, 4, TimeUnit.SECONDS);
    }
}

c. Fixed‑delay execution

public class ThreadPoolDemo10 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("Add time: " + LocalDateTime.now());
        service.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("Run task: " + LocalDateTime.now());
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }, 2, 4, TimeUnit.SECONDS);
    }
}

scheduleAtFixedRate uses the start time of the previous execution as the reference, while scheduleWithFixedDelay uses the end time, causing different spacing when tasks take longer.

5. Single‑thread scheduled executor

public class ThreadPoolDemo11 {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        System.out.println("Add time: " + LocalDateTime.now());
        service.schedule(() -> System.out.println("Run time: " + LocalDateTime.now()), 2, TimeUnit.SECONDS);
    }
}

6. Single‑thread pool

public class ThreadPoolDemo12 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadScheduledExecutor();
        for (int i = 0; i < 10; i++) {
            service.submit(() -> System.out.println("Thread name: " + Thread.currentThread().getName()));
        }
    }
}

A single‑thread pool reuses one thread, provides a task queue and rejection policy when the queue is full.

7. Work‑stealing pool (CPU‑based)

public class ThreadPoolDemo13 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newWorkStealingPool();
        for (int i = 0; i < 10; i++) {
            service.submit(() -> System.out.println("Thread name " + Thread.currentThread().getName()));
        }
    }
}

This pool creates a number of worker threads based on the number of available processors and uses a work‑stealing algorithm for load balancing.

8. Direct use of ThreadPoolExecutor

For full control over core pool size, maximum pool size, keep‑alive time, work queue, thread factory, and rejection handler, instantiate ThreadPoolExecutor directly. Detailed parameter explanations can be found in the referenced blog post.

Overall, choosing the appropriate thread‑pool creation method depends on the task characteristics, required concurrency level, and resource constraints.

JavaConcurrencythreadpoolExecutorServiceThreadPoolExecutorScheduledExecutor
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.