Skip to content

Thread Pool

Published: at 07:07 AM
Loading...

Core Principles of Thread Pool

Creating Thread Pools

Thread pool creation mainly uses two methods from the Executors utility class: newCachedThreadPool and newFixedThreadPool. The former creates a thread pool with no upper limit, while the latter creates a thread pool with an upper limit. Both return an ExecutorService object:

ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
ExecutorService e2 = Executors.newFixedThreadPool(3); //Create a thread pool that can contain at most 5 threads

Submitting Tasks to Thread Pool

Next, we’ll test whether the thread pool can really achieve the expected effect. Common methods for submitting tasks are submit or execute. Usually, execute(Runnable) submits tasks for execution only without returning results, while submit(Runnable) returns a Future that can be used to get execution status or exception information.

We perform the following test:

public class Main {
    public static void main(String[] args){
        //Create threads
        ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
        //ExecutorService e2 = Executors.newFixedThreadPool(5); //Create a thread pool that can contain at most 5 threads
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is running.");
            }
        };
        e1.submit(r);
    }
}

image.png

As shown above, the thread is already executing. Notice that the program doesn’t end, so we need to destroy the thread pool when it’s no longer needed, although generally thread pools don’t need to be closed.

Let’s test with more threads:

public class Main {
    public static void main(String[] args){
        //Create threads
        ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
        //ExecutorService e2 = Executors.newFixedThreadPool(5); //Create a thread pool that can contain at most 5 threads
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is running.");
            }
        };
        e1.submit(r);
        e1.submit(r);
        e1.submit(r);
        e1.submit(r);
        e1.submit(r);
    }
}

image.png

As shown, we added 5 tasks. For further testing, we’ll add a short sleep before adding tasks to verify whether thread objects that have finished execution in the thread pool can be reused:

package top.mygld.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        //Create threads
        ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
        //ExecutorService e2 = Executors.newFixedThreadPool(5); //Create a thread pool that can contain at most 5 threads
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is running.");
            }
        };
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
        Thread.sleep(500);
        e1.submit(r);
    }
}

image.png

As shown above, thread 1 is reused multiple times after completion.

Now let’s test newFixedThreadPool:

package top.mygld.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        //Create threads
        //ExecutorService e1 = Executors.newCachedThreadPool(); //Create an unlimited thread pool
        ExecutorService e2 = Executors.newFixedThreadPool(3); //Create a thread pool that can contain at most 5 threads
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " is running.");
            }
        };
        e2.submit(r);
        e2.submit(r);
        e2.submit(r);
        e2.submit(r);
        e2.submit(r);
    }
}

image.png

Perfect! As shown above, at most 3 threads can be used.

Thread Pool Destruction

For unused thread pools, simply use the shutdown method to destroy them. No demonstration here.


Custom Thread Pool

Besides using factory methods provided by Executors, we can also create a more flexible and controllable thread pool through ThreadPoolExecutor. Its constructor is as follows:

public ThreadPoolExecutor(
        int corePoolSize,        // Core thread count
        int maximumPoolSize,     // Maximum thread count
        long keepAliveTime,      // Maximum survival time for non-core threads
        TimeUnit unit,           // Time unit for survival time
        BlockingQueue<Runnable> workQueue, // Task queue
        ThreadFactory threadFactory,       // Thread factory for creating new threads
        RejectedExecutionHandler handler   // Rejection policy
)

Parameter Meanings

  1. corePoolSize Core thread count, the number of threads the thread pool will always maintain, even if these threads are idle.
  2. maximumPoolSize Maximum thread count, the maximum number of threads the thread pool can expand to when there are too many tasks.
  3. keepAliveTime & unit Survival time for non-core threads (beyond corePoolSize) when idle, they will be recycled after timeout.
  4. workQueue Blocking queue for storing tasks waiting to be executed. Common choices:
    • ArrayBlockingQueue (bounded queue, array implementation)
    • LinkedBlockingQueue (unbounded queue, linked list implementation)
    • SynchronousQueue (direct task submission, no storage)
  5. threadFactory Defines how threads are created, such as setting thread names, whether they are daemon threads, etc. Default factory is sufficient.
  6. handler Rejection policy when both thread pool and queue are full:
    • AbortPolicy (default, throws exception)
    • CallerRunsPolicy (caller thread executes the task)
    • DiscardPolicy (directly discard task)
    • DiscardOldestPolicy (discard oldest task in queue)

Example: Custom Thread Pool

package top.mygld.demo;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        // Custom thread pool
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,                      // Core thread count
                5,                      // Maximum thread count
                10, TimeUnit.SECONDS,   // Non-core thread idle survival time
                new ArrayBlockingQueue<>(3),  // Bounded task queue, stores at most 3 tasks
                Executors.defaultThreadFactory(), // Default thread factory
                new ThreadPoolExecutor.AbortPolicy() // Rejection policy: throw exception directly
        );

        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName() + " is executing task");
            try {
                Thread.sleep(2000); // Simulate time-consuming task
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        // Submit 10 tasks, observe how thread pool schedules them
        for (int i = 1; i <= 10; i++) {
            try {
                pool.execute(task);
            } catch (RejectedExecutionException e) {
                System.out.println("Task " + i + " was rejected!");
            }
        }

        pool.shutdown(); // Close thread pool
    }
}

c11effa6-8ac4-4dab-acd1-bf8f0085253c.png

Execution Flow Explanation

Maximum Parallelism

Meaning of Maximum Parallelism

Maximum parallelism can be obtained through the following code:

public class Main {
    public static void main(String[] args) {
        int i = Runtime.getRuntime().availableProcessors();
        System.out.println(i);
    }
}

Appropriate Thread Pool Size

CPU-Intensive Computing

Maximum parallelism + 1

I/O-Intensive Computing

Maximum parallelism×Expected CPU utilization×Total time(CPU compute time+wait time)CPU compute timeMaximum\ parallelism \times Expected\ CPU\ utilization \times \frac{Total\ time(CPU\ compute\ time + wait\ time)}{CPU\ compute\ time}

Interview Key Points

These topics are frequently asked in interviews and will be organized later.


Previous Post
Vocabulary and Idiom Accumulation
Next Post
Java Dynamic Proxy