3

previous article 160a69ca220245 introduced the Thread class , it can be seen that the thread is destroyed as the execution of the task ends. However, because the creation and destruction of threads involve system calls, the overhead is relatively high, so the life cycle of the thread needs to be decoupled from the task. Using the thread pool to manage threads can effectively reuse threads to perform tasks. This article will introduce the most basic implementation class of thread pool ThreadPoolExecutor.

This article is based on jdk1.8.0_91

1. Thread Pool System

Executor体系

类型      名称                             描述

接口      Executor                        最上层的接口,提供了任务提交的基础方法
接口      ExecutorService                 继承了 Executor 接口,扩展了提交任务、获取异步任务执行结果、线程池销毁等方法
接口      ScheduledExecutorService        继承了 ExecutorService 接口,增加了延迟执行任务、定时执行任务的方法
抽象类    AbstractExecutorService         提供了 ExecutorService 接口的默认实现,提供 newTaskFor 方法将任务转换为 RunnableFuture,以便提交给 Executor 执行
实现类    ThreadPoolExecutor              基础、标准的线程池实现
实现类    ScheduledThreadPoolExecutor     继承了 ThreadPoolExecutor,实现了 ScheduledExecutorService 中相关延迟任务、定时任务的方法
实现类    ForkJoinPool                    JDK7 加入的线程池,是 Fork/Join 框架的核心实现,只允许执行 ForkJoinTask 任务
普通类    Executors                       创建各种线程池的工具类

The thread pool can solve two problems:

  • Reduce the system overhead caused by frequent creation and destruction of threads.
  • Automatically manage threads and allocate resources, and users only need to submit tasks.

2. Construction method

It is recommended to use the more convenient Executors factory method in the JDK, which have predefined settings for most usage scenarios:

  • Executors.newCachedThreadPool(): Unbounded thread pool, which can automatically recycle threads
  • Executors.newFixedThreadPool(int): fixed-size thread pool
  • Executors.newSingleThreadExecutor(): a thread pool of a single background thread
  • Executors.newScheduledThreadPool(): The thread pool that executes scheduled tasks
  • Executors.newWorkStealingPool(int): thread pool that supports parallel execution

Ali Java Development Manual limits the use of thread pools, which can be used as a reference:

[Mandatory] Thread resources must be provided through the thread pool, and it is not allowed to explicitly create threads in the application.
Description: The advantage of using the thread pool is to reduce the time spent on creating and destroying threads and the overhead of system resources, and to solve the problem of insufficient resources. If the thread pool is not used, it may cause the system to create a large number of threads of the same type and cause the problem of running out of memory or "over switching".

[Mandatory] Thread pools are not allowed to be created using Executors, but are created through ThreadPoolExecutor. This processing method allows students to write more clearly the operating rules of the thread pool and avoid the risk of resource exhaustion.
Description: The disadvantages of the thread pool object returned by Executors are as follows:
1) FixedThreadPool and SingleThreadPool: The allowed request queue length is Integer.MAX_VALUE, which may accumulate a large number of requests, resulting in OOM.
2) CachedThreadPool and ScheduledThreadPool: The allowed number of threads to be created is Integer.MAX_VALUE, which may create a large number of threads, resulting in OOM.

2.1 Source code analysis

Executors internally are implemented by calling the ThreadPoolExecutor construction method:

/**
 * Creates a new {@code ThreadPoolExecutor} with the given initial
 * parameters.
 *
 * @param corePoolSize    核心线程数量
 * @param maximumPoolSize 总的线程数量
 * @param keepAliveTime   空闲线程的存活时间
 * @param unit            keepAliveTime的单位
 * @param workQueue       任务队列, 保存已经提交但尚未被执行的线程
 * @param threadFactory   线程工厂(用于指定如何创建一个线程)
 * @param handler         拒绝策略 (当任务太多导致工作队列满时的处理策略)
 */
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                          BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

2.2 Instructions for use

Java official instructions on the use of ThreadPoolExecutor:

2.2.1 Core and maximum pool sizes

ThreadPoolExecutor will automatically adjust the number of threads in the thread pool according to corePoolSize and maximumPoolSize, and determine whether tasks are queued.

When submitting a new task:

  • If the running thread is less than corePoolSize, a new thread is created to process the request, even if there is a worker thread that is idle, it will not be queued.
  • If there are more threads running than corePoolSize but less than maximumPoolSize:

    • If the queue is not full, the newly submitted task will be added to the queue without creating a new thread.
    • If the queue is full and the number of running threads is less than the maximumPoolSize, a new thread will be created to perform the task.
    • If the set corePoolSize and maximumPoolSize are the same, a fixed-size thread pool is created.
  • If the number of threads is greater than the maximumPoolSize, the newly submitted task will be processed according to the rejection policy.

    • If the maximumPoolSize is set to a basic unbounded value (such as Integer.MAX_VALUE), the pool is allowed to adapt to any number of concurrent tasks.

In most cases, corePoolSize and maximumPoolSize are set by the constructor, but you can also use ThreadPoolExecutor's setCorePoolSize(int) and setMaximumPoolSize(int) for dynamic changes. Note that core threads and non-core threads are just a logical distinction. There is only one type of thread in the thread pool, called Worker.

2.2.2 On-demand construction

By default, core threads are only created and started when new tasks arrive.
It can be dynamically rewritten using the method prestartCoreThread() or prestartAllCoreThreads().
Generally, when constructing a thread pool with a non-empty queue, you will want to start the thread first.

2.2.3 Creating new threads

Use ThreadFactory to create new threads.
Executors.defaultThreadFactory() is used to create threads by default in Executors, and these threads have the same grouping and priority, and they are all non-daemon threads.
If the ThreadFactory fails to create a new thread, the thread pool can continue to run but cannot perform any tasks.

2.2.4 Keep-alive times

By default, the keep-alive policy is only applied to non-core threads, that is, when the idle time of the thread exceeds keepAliveTime, it will be terminated.
If you use the allowCoreThreadTimeOut(boolean) method, the keep-alive strategy will also be applied to the core thread.
When the thread pool is in an inactive state, resource consumption can be reduced. If the thread becomes active again, a new thread will be created.
You can use the method setKeepAliveTime(time, timeUnit) to dynamically change this parameter. If Long.MAX_VALUE and TimeUnit.NANOSECONDS are used as input parameters, the idle thread will not be terminated unless the thread pool is closed.

2.2.5 Queuing

All BlockingQueues can be used to transfer and hold submitted tasks. When the number of threads in the thread pool is greater than corePoolSize but less than maximumPoolSize, new tasks will be added to the synchronization queue.

There are three general strategies for queuing:

  • Direct handoffs

    • The delivery queue SynchronousQueue is used by default, which will pass tasks directly to threads without saving tasks.
    • If no thread is currently available, a new thread will be created. Usually an unbounded maximum number of threads (maximumPoolSize) is required to avoid rejecting newly submitted tasks.
    • In extreme cases, CPU and memory resources will be exhausted due to the creation of too many threads.
  • Unbounded queues

    • Use an unbounded queue (such as LinkedBlockingQueue) as a waiting queue. When all core threads are processing tasks, the newly submitted tasks will enter the queue and wait.
    • The number of threads will not exceed corePoolSize, that is, the value of maximumPoolSize is invalid.
    • When each task is completely independent of other tasks, that is, when the task execution does not affect each other, it is suitable to use an unbounded queue.
    • In extreme cases, memory resources will be exhausted due to storing too many tasks.
  • Bounded queues

    • Using a bounded queue (such as ArrayBlockingQueue) as a waiting queue helps prevent resource exhaustion, but it may be difficult to adjust and control the queue size and thread pool size (maximumPoolSize).
    • Using a large queue and a small number of threads can reduce the overhead of CPU usage, system resources, and context switching, but it will result in lower throughput.
    • Using a small queue usually requires more threads, which can maximize CPU usage, but may require greater scheduling overhead, thereby reducing throughput.

2.2.6 Rejected tasks (rejected policy)

When the thread pool is closed, or the number of threads and queue capacity in the thread pool are saturated, continuing to submit new tasks will be rejected, and the RejectedExecutionHandler#rejectedExecution method will be triggered.

ThreadPoolExecutor defines four rejection strategies:

  • AbortPolicy: The default policy, which throws RejectedExecutionException when a task needs to be rejected;
  • CallerRunsPolicy: The thread that submits the task executes the task by itself to slow down the submission of new tasks. If the thread pool has been closed, the task will be discarded;
  • DiscardPolicy: directly discard the task;
  • DiscardOldestPolicy: Discard the longest waiting task in the queue and execute the currently submitted task. If the thread pool is closed, the task will be discarded.

You can customize the RejectedExecutionHandler class to implement the rejection strategy. It should be noted that the operation of the rejection policy requires the capacity of the thread pool and queue to be specified.

2.2.7 Hook methods

ThreadPoolExecutor provides beforeExecute and afterExecute methods, which are called before and after each task is executed. Provide a terminated method to do some finishing work before the thread pool is closed.
If the hook method throws an exception, the internal worker threads will fail and terminate in turn.

2.2.8 Queue maintenance

The method getQueue() allows access to the work queue for monitoring and debugging purposes. We strongly discourage the use of this method for any other purpose.
The two methods remove() and purge() can be used to help with storage recovery when canceling a large number of queued tasks.

2.2.9 Finalization

When the thread pool reference becomes unreachable, and there are no remaining threads in the thread pool (by setting allowCoreThreadTimeOut to destroy inactive core threads), the thread pool will automatically shut down at this time.

3. Properties

3.1 Thread pool status

ThreadPoolExecutor uses a variable ctl of type AtomicInteger to manage the thread pool.
Among them, the low 29 bits hold the number of threads, and the high 3 bits hold the thread pool status.
The maximum number of threads in the thread pool is 2^29-1.

/**
 * The main pool control state, ctl, is an atomic integer packing
 * two conceptual fields
 *   workerCount, indicating the effective number of threads      // 工作线程数量
 *   runState,    indicating whether running, shutting down etc   // 线程池运行状态
 */
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

private static final int COUNT_BITS = Integer.SIZE - 3;      // 32 - 3 = 29
// 最大线程数: 2^29-1
private static final int CAPACITY   = (1 << COUNT_BITS) - 1; // 0001 1111 1111 1111 1111 1111 1111 1111

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS; // 高三位 111
private static final int SHUTDOWN   =  0 << COUNT_BITS; // 高三位 000
private static final int STOP       =  1 << COUNT_BITS; // 高三位 001
private static final int TIDYING    =  2 << COUNT_BITS; // 高三位 010
private static final int TERMINATED =  3 << COUNT_BITS; // 高三位 011

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; } // 运行状态
private static int workerCountOf(int c)  { return c & CAPACITY; }  // 运行的工作线程数
private static int ctlOf(int rs, int wc) { return rs | wc; }       // 封装运行状态和任务线程

ThreadPoolExecutor defines a total of 5 thread pool states:

  • RUNNING: Can receive new tasks and queue tasks
  • SHUTDOWN: do not receive new tasks, but will process tasks in the queue
  • STOP: Do not receive new tasks, nor process tasks in the queue, and interrupt running tasks
  • TIDYING: All tasks have been terminated, workerCount is 0, when the pool status is TIDYING, the terminated() method will be called to process the finishing work.
  • TERMINATED: Indicates that the terminated() method has completed its execution.

The transfer of thread pool state:

  • RUNNING -> SHUTDOWN

On invocation of shutdown(), perhaps implicitly in finalize()

  • (RUNNING or SHUTDOWN) -> STOP

On invocation of shutdownNow()

  • SHUTDOWN -> TIDYING

When both queue and pool are empty

  • STOP -> TIDYING

When pool is empty

  • TIDYING -> TERMINATED

When the terminated() hook method has completed

线程池状态转移

3.2 Worker

// 工作线程集合
private final HashSet<Worker> workers = new HashSet<Worker>();

// 操作 workers 集合使用到的锁
private final ReentrantLock mainLock = new ReentrantLock();

There is a Worker collection in the thread pool, and a Worker corresponds to a worker thread. When the thread pool is started, the corresponding Worker will execute the tasks in the pool, and obtain a new task from the blocking queue to continue execution after execution.

The Worker collection can only be operated when the mainLock is held. The official Java instructions for using mainLock instead of concurrent collection:

  • When the thread pool is closed, the threads in the Worker collection can be checked and interrupted serially to avoid interrupt storms.
  • Can simplify some statistical operations, such as largestPoolSize.

Worker is an internal class of ThreadPoolExecutor, and its inheritance system is as follows:

  • Inherited the AQS abstract class and realized the non-reentrant mutex. Worker threads need to hold Worker locks when performing tasks, and the lock objects held by each worker thread are different.
  • The Runnable interface is implemented, which means that the Worker itself is also executed as a thread task.

继承体系 Worker uses the state attribute in AQS to indicate whether to hold a lock:

  • -1: Initial state
  • 0: No lock state
  • 1: Locked state

When the Worker starts to work, it will first execute the unlock() method to set the state to 0, and then use CAS to lock the state. See ThreadPoolExecutor#runWorker for details.

Note that the worker threads in the thread pool are logically divided into core threads and non-core threads, but there is no relevant attribute in the Worker class to mark whether the current thread is a core thread!

Instead, it is dynamically specified during runtime:

  1. ThreadPoolExecutor#execute When submitting a task, call addWorker to add a new worker thread. If the parameter core is passed as true, it is only used to verify whether the number of core threads corePoolSize is legal, and does not mean that the thread is always a core thread.
  2. ThreadPoolExecutor#getTask When getting a task, if workerCount> corePoolSize is established, it means that the current thread should pull tasks from the queue according to the rules of non-core threads (the thread will be destroyed if the task cannot be pulled within the keepAliveTime), regardless of the thread Whether to specify core as true when addWorker is created.

The purpose of this design is to dynamically maintain the number of core threads in the thread pool not to exceed corePoolSize, which is a loose control.

java.util.concurrent.ThreadPoolExecutor.Worker

private final class Worker // 不可重入的互斥锁
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread; // 工作线程
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;  // 初始运行任务
    /** Per-thread task counter */
    volatile long completedTasks; // 任务完成计数

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.

    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }

    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

3.3 ThreadFactory ThreadFactory

ThreadPoolExecutor has the attribute threadFactory which represents the thread factory.

/**
 * Factory for new threads. All threads are created using this
 * factory (via method addWorker).  All callers must be prepared
 * for addWorker to fail, which may reflect a system or user's
 * policy limiting the number of threads.  Even though it is not
 * treated as an error, failure to create threads may result in
 * new tasks being rejected or existing ones remaining stuck in
 * the queue.
 *
 * We go further and preserve pool invariants even in the face of
 * errors such as OutOfMemoryError, that might be thrown while
 * trying to create threads.  Such errors are rather common due to
 * the need to allocate a native stack in Thread.start, and users
 * will want to perform clean pool shutdown to clean up.  There
 * will likely be enough memory available for the cleanup code to
 * complete without encountering yet another OutOfMemoryError.
 */
private volatile ThreadFactory threadFactory;

Worker contains the attribute thread to represent the worker thread. In the Worker constructor, the thread is created through the thread factory (ie Thread#new, be careful not to start the thread!).

/**
 * Creates with given first task and thread from ThreadFactory.
 * @param firstTask the first task (null if none)
 */
Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

The internal class DefaultThreadFactory is defined in the Executors tool class as the default thread factory, which is used to uniformly set thread information:

  • If there is a SecurityManager, the thread group is System#getSecurityManager, otherwise it is the same as the group of the thread that called the defaultThreadFactory method;
  • Thread priority is Thread.NORM_PRIORITY (between minimum and maximum);
  • Thread naming is unified in the format of pool-N-thread-M, where N is the serial number of this factory, and M is the serial number of the thread created by this factory.

java.util.concurrent.Executors#defaultThreadFactory

/**
 * Returns a default thread factory used to create new threads.
 * This factory creates all new threads used by an Executor in the
 * same {@link ThreadGroup}. If there is a {@link
 * java.lang.SecurityManager}, it uses the group of {@link
 * System#getSecurityManager}, else the group of the thread
 * invoking this {@code defaultThreadFactory} method. Each new
 * thread is created as a non-daemon thread with priority set to
 * the smaller of {@code Thread.NORM_PRIORITY} and the maximum
 * priority permitted in the thread group.  New threads have names
 * accessible via {@link Thread#getName} of
 * <em>pool-N-thread-M</em>, where <em>N</em> is the sequence
 * number of this factory, and <em>M</em> is the sequence number
 * of the thread created by this factory.
 * @return a thread factory
 */
public static ThreadFactory defaultThreadFactory() {
    return new DefaultThreadFactory();
}

java.util.concurrent.Executors.DefaultThreadFactory

/**
 * The default thread factory
 */
static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

4. Thread pool method

4.1 Submit task execute

There is only one execute method in the top-level interface Executor of the thread pool, which is used to submit tasks to the thread pool, and ThreadPoolExecutor implements it.

Method description:

  • Submit a task to the thread pool, the task may not be executed immediately.
  • The submitted task may be executed in a new thread, or it may be executed in an existing idle thread.
  • If the task cannot be submitted because the pool is closed or the pool capacity has been saturated, then the task is processed according to the rejection policy RejectedExecutionHandler.

Code flow:

  1. If the task is empty, throw a NullPointerException.
  2. If worderCount <corePoolSize, a new core thread is added through addWorker, and the current task is executed as its firstTask.
  3. If worderCount >= corePoolSize, it means that a new core thread cannot be added, and the task needs to be added to the synchronization queue. Divided into two situations:
  4. If joining the team is successful, you need to do a double check:
    4.1 When the thread pool is closed during the enqueue process, the enqueue operation shall be rolled back and the rejection strategy shall be implemented.
    4.2 The worker thread has died in the process of enqueuing, when the number of worker threads is 0, the non-core thread needs to be initialized to pull the tasks in the queue for processing.
  5. If the enqueue fails, try to create a non-core thread for processing the task. If the creation of the non-core thread fails, the rejection strategy is executed.

java.util.concurrent.ThreadPoolExecutor#execute

/**
 * Executes the given task sometime in the future.  The task
 * may execute in a new thread or in an existing pooled thread.
 *
 * If the task cannot be submitted for execution, either because this
 * executor has been shutdown or because its capacity has been reached,
 * the task is handled by the current {@code RejectedExecutionHandler}.
 *
 * @param command the task to execute
 * @throws RejectedExecutionException at discretion of
 *         {@code RejectedExecutionHandler}, if the task
 *         cannot be accepted for execution
 * @throws NullPointerException if {@code command} is null
 */
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) // 添加新的核心线程,并把当前任务交给它执行
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {  // 线程池未关闭,且非阻塞入队成功,则进入下一步
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command)) // 线程池已关闭,不接收新任务,需要出队并拒绝任务
            reject(command);
        else if (workerCountOf(recheck) == 0)        // 入队成功但工作线程为空,则添加非核心线程且不指定任务
            addWorker(null, false);                  // 没有任务的工作线程会从同步队列中拉取任务去执行
    }
    else if (!addWorker(command, false)) // 队列已满,且创建非核心线程失败,则拒绝任务
        reject(command);
}

4.2 add worker thread addWorker

In the task submission (execute method), update the number of core threads (setCorePoolSize method), pre-start thread (prestartCoreThread method) will call the addWorker method to add a new worker thread.

The addWorker input parameter specifies the tasks that the worker thread needs to perform, and whether the worker thread is a core thread.

The main flow of the code:

  1. By checking the status of the thread pool and the limit on the number of threads, you can determine whether you can add a worker thread.
  2. Create a worker thread (Worker#new) and start a worker thread (Thread#start).

Check the thread pool status (Note: SHUTDOWN does not receive new tasks, but will process tasks in the queue):

  1. The thread pool status is STOP or TIDYING or TERMINATED: no task will be executed, and new threads cannot be created;
  2. The thread pool status is SHUTDOWN and firstTask != null: Because the submission of new tasks is no longer accepted, a new thread cannot be created;
  3. The thread pool status is SHUTDOWN and the queue is empty: because there are no more tasks in the queue, there is no need to perform any tasks, and no new threads can be created.

Check the limit on the number of threads (Note: Worker threads are logically divided into core threads and non-core threads):

  1. If the number of worker threads exceeds CAPACITY (ie 2^29-1), you cannot create a new [worker thread];
  2. If the number of worker threads exceeds corePoolSize, a new [core thread] cannot be created;
  3. If the number of worker threads exceeds the maximumPoolSize, you cannot create a new [non-core thread];

java.util.concurrent.ThreadPoolExecutor#addWorker

/**
 * Checks if a new worker can be added with respect to current
 * pool state and the given bound (either core or maximum). If so,
 * the worker count is adjusted accordingly, and, if possible, a
 * new worker is created and started, running firstTask as its
 * first task. This method returns false if the pool is stopped or
 * eligible to shut down. It also returns false if the thread
 * factory fails to create a thread when asked.  If the thread
 * creation fails, either due to the thread factory returning
 * null, or due to an exception (typically OutOfMemoryError in
 * Thread.start()), we roll back cleanly.
 *
 * @param firstTask the task the new thread should run first (or
 * null if none). Workers are created with an initial first task
 * (in method execute()) to bypass queuing when there are fewer
 * than corePoolSize threads (in which case we always start one),
 * or when the queue is full (in which case we must bypass queue).
 * Initially idle threads are usually created via
 * prestartCoreThread or to replace other dying workers.
 *
 * @param core if true use corePoolSize as bound, else
 * maximumPoolSize. (A boolean indicator is used here rather than a
 * value to ensure reads of fresh values after checking other pool
 * state).
 * @return true if successful
 */
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary. // 检查线程池状态
        if (rs >= SHUTDOWN && 
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c); // 检查线程数量限制
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c)) // workerCount 自增,结束自旋
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs) // 线程池状态发生变化,重试
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask); // 创建工作线程,指定初始任务(Executors.DefaultThreadFactory 中会执行 Thread#new,但是不会调用 Thread#start)
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock(); // 加锁用于操作 workers 集合
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException(); // 如果线程工厂已经提前启动线程了(Thread#start),则报错
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s; // 更新最大池容量
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start(); // 启动线程,由 JVM 在操作系统层面创建线程并执行 Thread#run
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w); // 线程添加失败,回滚操作
    }
    return workerStarted;
}

Note that in the process of creating a new thread, you need to distinguish the difference between new Thread() and new Thread().start :

  • Thread#new: Create a Thread object, and it will not be mapped to a thread on the operating system. At this time, Thread#isAlive is false. Note that the thread factory in the thread pool can only create Thread objects and cannot start threads.
  • Thread#start: Start the thread. The JVM creates the thread at the operating system level and binds it to the Thread object. At this time, Thread#isAlive is true.

Thread t = w.thread; t.start() in the ThreadPoolExecutor#addWorker method will trigger the execution of ThreadPoolExecutor#runWorker. The process is simplified as follows:

private final class Worker implements Runnable {

    final Thread thread; // 工作线程

    public Worker() {
        thread = new Thread(this);
        System.out.println("addWorker!");
    }

    @Override
    public void run() {
        System.out.println("runWorker!");
    }
}

/**
 * 测试在 addWorker 中触发 runWorker
 */
@Test
public void test() throws InterruptedException {
    Worker worker = new Worker();
    worker.thread.start();
    worker.thread.join();
}

4.3 Perform task runWorker

After adding a worker thread in ThreadPoolExecutor#addWorker, the worker thread will be started (Thread#start), and the worker thread will be triggered to execute the task (Thread#run).

runWorker code flow:

  1. Get the task. The task may be a firstTask or a task pulled from the queue.
  2. Acquire the mutex lock on the worker to ensure that no other thread can interrupt the current task unless the thread pool is closed.
  3. Check the thread pool status. If it is STOP or TIDYING or TERMINATED, it means that the task is no longer needed, and the current thread is interrupted.
  4. Perform pre-work beforeExecute, which is a hook method.
  5. Perform the task Runnable#run.
  6. Perform post-work afterExecute, which is also a hook method.

For Worker#lock, the official description:

Before running any task, the lock is acquired to prevent other pool interrupts while the task is executing, and then we ensure that unless pool is stopping, this thread does not have its interrupt set.

java.util.concurrent.ThreadPoolExecutor#runWorker

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts // 初始化 state 为 0
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) { // firstTask 不为空,或者从队列拉取到任务不为空
            w.lock(); // 加锁,确保除非线程池关闭,否则没有其他线程能够中断当前任务
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) && // 如果线程池状态 >= STOP,则中断当前线程,不需要执行新任务
                !wt.isInterrupted())                    // 这里可能会两次执行 isInterrupted,是为了避免 shutdownNow 过程中清除了线程中断状态
                wt.interrupt();
            try {
                beforeExecute(wt, task); // 前置工作,预留
                Throwable thrown = null;
                try {
                    task.run(); // 执行任务
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown); // 后置工作,预留
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false; // 走到这里说明线程没有任务可执行
    } finally {
        processWorkerExit(w, completedAbruptly); // 工作线程退出
    }
}

4.4 Get task getTask

In ThreadPoolExecutor#runWorker, before the worker thread executes the task, if firstTask is empty, call getTask() to get the task from the queue.

Before a worker thread pulls a task from the queue, it needs to be checked. If any of the following situations occurs, it will exit directly:

  1. The number of worker threads is greater than the maximumPoolSize;
  2. The thread pool has been stopped (STOP);
  3. The thread pool is closed (SHUTDOWN) and the queue is empty;
  4. The worker thread waits for the task to time out (keepAliveTime).

java.util.concurrent.ThreadPoolExecutor#getTask

/**
 * Performs blocking or timed wait for a task, depending on
 * current configuration settings, or returns null if this worker
 * must exit because of any of:
 * 1. There are more than maximumPoolSize workers (due to
 *    a call to setMaximumPoolSize).
 * 2. The pool is stopped.
 * 3. The pool is shutdown and the queue is empty.
 * 4. This worker timed out waiting for a task, and timed-out
 *    workers are subject to termination (that is,
 *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
 *    both before and after the timed wait, and if the queue is
 *    non-empty, this worker is not the last thread in the pool.
 *
 * @return task, or null if the worker must exit, in which case
 *         workerCount is decremented
 */
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { // 校验线程池状态:1.线程池状态为 SHUTDOWN 且队列为空;2.线程池状态 >= STOP
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 当前线程是否允许超时,true 表示具有超时时间(keepAliveTime)

        if ((wc > maximumPoolSize || (timed && timedOut)) // 校验工作线程状态:1.工作线程数超过 maximumPoolSize;2.当前工作线程已超时;3.队列为空
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // 阻塞直到拉取成功或超时
                workQueue.take(); // 阻塞直到拉取成功
            if (r != null)
                return r;
            timedOut = true; // 表示直到超时,都没有获取任务
        } catch (InterruptedException retry) { // 拉取时被中断唤醒,继续自旋
            timedOut = false;
        }
    }
}

There are two ways for worker threads to pull tasks from the thread pool:

  1. workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : Block until the pull task succeeds or times out.
  2. workQueue.take() : Block until the pull task is successful.

The code allowCoreThreadTimeOut || wc > corePoolSize the expression 060a69ca221569 to control which pull task method is used.
When this expression is true, if the thread does not pull the task within the keepAliveTime time, it will be destroyed, which will behave as a "non-core thread".
However, since the number of worker threads wc will change in real time, the same thread may use different methods to pull tasks in succession during its operation.
In other words, the worker thread may switch between "core thread" and "non-core thread" during operation.

  • Core -> Non-core: The current thread is the core thread at the time of initial addWorker(). When the queue is full, a non-core thread is added to the pool. At this time, the current thread executes getTask() to satisfy wc> corePoolSize and becomes a non-core thread.
  • Non-core -> core: The current thread is a non-core thread at the time of initial addWorker(). When some core threads are terminated due to an exception in the execution of a task, the current thread executing getTask() does not satisfy wc> corePoolSize and becomes a core thread.

In fact, ThreadPoolExecutor distinguishes between "core threads" and "non-core threads" just to use corePoolSize to control the number of active threads and whether tasks are queued in the queue. It does not care whether Workers are "core threads".

4.5 The worker thread exits processWorkerExit

In runWorker(), if an idle thread is identified through getTask() (timedOut = true), or the worker thread is abnormal during the execution of the task, processWorkerExit() will be called to exit the worker thread.

Code flow:

  1. If the current thread is terminated due to abnormal task execution, workerCount needs to be deducted.
  2. Acquire mainLock, count the number of tasks, and remove the current worker from the workers set.
  3. Attempt to terminate the thread pool.
  4. If the thread pool is not terminated, it is necessary to determine whether to add new non-core threads.

Note that the current thread will automatically end after executing the processWorkerExit method, and Thread#isAlive returns false.
Therefore, before the current thread terminates, if one of the following conditions is met, a new non-core thread will be created to replace the current thread:

  1. The user task execution exception caused the thread to exit.
  2. The number of worker threads is less than corePoolSize.
  3. The waiting queue is not empty but there are no worker threads.

Java official instructions:

replaces the worker if either it exited due to user task exception or if fewer than corePoolSize workers are running or queue is non-empty but there are no workers.

java.util.concurrent.ThreadPoolExecutor#processWorkerExit

/**
 * @param w the worker
 * @param completedAbruptly if the worker died due to user exception
 */
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted // 当前线程执行任务时出现异常,需要扣减 workerCount
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks; // 统计所有线程完成的任务数
        workers.remove(w); // 移除当前线程的 worker
    } finally {
        mainLock.unlock();
    }

    tryTerminate(); // 尝试终止线程池

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) { // RUNNING、SHUTDOWN,即线程池尚未停止
        if (!completedAbruptly) {    
            // 没有出现异常,说明当前线程是非活跃线程:
            // 1. allowCoreThreadTimeOut 为 false,则 min 为 corePoolSize。若 workerCountOf(c) >= min 说明当前终止的是非核心线程,无需补充新线程
            // 2. allowCoreThreadTimeOut 为 true,且队列为空,则 min 为 0。 若 workerCountOf(c) >= min 说明当前没有任务需要处理,无需补充新线程
            // 3. allowCoreThreadTimeOut 为 true,且队列非空,则 min 为 1。 若 workerCountOf(c) >= min 说明具有活跃的线程处理任务,无需补充新线程
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false); // 创建新的线程替换当前线程
    }
}

4.6 Try to close the thread pool tryTerminate

tryTerminate is used to try to terminate the thread pool. This method is used to terminate the thread pool in shutdown(), shutdownNow(), and remove().
This method must be called after any behavior that may cause the thread to terminate, such as reducing the number of worker threads, removing tasks from the queue, or processing the worker thread exit logic after the worker thread has finished running (processWorkerExit).

Code flow:

  1. Verify the thread pool status. When the thread pool status is STOP, or the status is SHUTDOWN and the queue is empty, it means that the thread pool can be terminated, and then you can enter the next step.
  2. Check the number of threads. If the number of worker threads in the thread pool is not 0, interrupt one of the threads (interruptIdleWorkers) and end the tryTerminate method, and then the thread will pass the thread pool close message (runWorker -> getTask -> processWorkerExit -> tryTerminate).
  3. When there are no worker threads in the thread pool and there are no tasks in the queue, start to close the thread pool:
    3.1 Modify thread pool status: (STOP or SHUTDOWN) -> TIDYING
    3.2 Call the hook method terminated()
    3.3 Modify the thread pool status: TIDYING -> TERMINATED

java.util.concurrent.ThreadPoolExecutor#tryTerminate

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 校验线程池状态,只有状态为 STOP,或者(状态为 SHUTDOWN 且队列为空)的情况下,才可以往下执行,否则直接返回
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) || // TIDYING、TERMINATED
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE); // 仅中断一个工作线程,由它来传递线程池关闭消息
            return;
        }

        final ReentrantLock mainLock = this.mainLock; // 来到这里,说明线程池中没有工作线程了
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { // (STOP or SHUTDOWN) -> TIDYING
                try {
                    terminated(); // 钩子方法
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0)); // TIDYING -> TERMINATED
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

4.7 Close the thread pool

After understanding how tryTerminate() tries to close the thread pool, let’s look at the methods for initiating the thread pool shutdown: shutdown(), shutdownNow().

4.7.1 shutdown

Close the thread pool, do not receive new tasks, but will process the tasks in the queue.

java.util.concurrent.ThreadPoolExecutor#shutdown

/**
 * Initiates an orderly shutdown in which previously submitted
 * tasks are executed, but no new tasks will be accepted.
 * Invocation has no additional effect if already shut down.
 *
 * <p>This method does not wait for previously submitted tasks to
 * complete execution.  Use {@link #awaitTermination awaitTermination}
 * to do that.
 *
 * @throws SecurityException {@inheritDoc}
 */
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();     // 检查关闭权限
        advanceRunState(SHUTDOWN); // 修改线程池状态
        interruptIdleWorkers();    // 依次中断所有空闲线程
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate(); // 尝试关闭线程池
}

To understand how shutdown() does not accept new tasks before closing the thread pool, and continue to process existing tasks, the key lies in two operations:

advanceRunState(SHUTDOWN)

After setting the thread pool status to SHUTDOWN:

  • In ThreadPoolExecutor#execute, the thread pool state is SHUTDOWN and will not receive new tasks.
  • In ThreadPoolExecutor#getTask, the thread pool status is SHUTDOWN but there are still unprocessed tasks in the queue, and the task will continue to be pulled for processing.
  • In ThreadPoolExecutor#runWorker, the thread pool status is SHUTDOWN and you can continue to process tasks.
interruptIdleWorkers

java.util.concurrent.ThreadPoolExecutor#interruptIdleWorkers()

/**
 * Common form of interruptIdleWorkers, to avoid having to
 * remember what the boolean argument means.
 */
private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

/**
 * Interrupts threads that might be waiting for tasks (as
 * indicated by not being locked) so they can check for
 * termination or configuration changes. Ignores
 * SecurityExceptions (in which case some threads may remain
 * uninterrupted).
 *
 * @param onlyOne If true, interrupt at most one worker. This is
 * called only from tryTerminate when termination is otherwise
 * enabled but there are still other workers.  In this case, at
 * most one waiting worker is interrupted to propagate shutdown
 * signals in case all threads are currently waiting.
 * Interrupting any arbitrary thread ensures that newly arriving
 * workers since shutdown began will also eventually exit.
 * To guarantee eventual termination, it suffices to always
 * interrupt only one idle worker, but shutdown() interrupts all
 * idle workers so that redundant workers exit promptly, not
 * waiting for a straggler task to finish.
 */
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) { // 能够获取锁,说明当前线程没有在执行任务,是“空闲”的
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

interruptIdleWorkers() Before interrupting the thread, use tryLock() to try to acquire the lock once and then interrupt the task.

  • If the target thread pulls a task in ThreadPoolExecutor#getTask, since the pull task does not need to hold a lock, the target thread will be awakened by an interrupt while waiting for the task, and re-spin check and then pull the task.
  • If the target thread executes the task in ThreadPoolExecutor#runWorker, because the execution of the task needs to hold the lock, other threads tryLock() fail, and the current target thread can safely complete the task.

4.7.2 shutdownNow

Close the thread pool, do not receive new tasks, will not process tasks in the queue, and interrupt running tasks.

java.util.concurrent.ThreadPoolExecutor#shutdownNow

/**
 * Attempts to stop all actively executing tasks, halts the
 * processing of waiting tasks, and returns a list of the tasks
 * that were awaiting execution. These tasks are drained (removed)
 * from the task queue upon return from this method.
 *
 * <p>This method does not wait for actively executing tasks to
 * terminate.  Use {@link #awaitTermination awaitTermination} to
 * do that.
 *
 * <p>There are no guarantees beyond best-effort attempts to stop
 * processing actively executing tasks.  This implementation
 * cancels tasks via {@link Thread#interrupt}, so any task that
 * fails to respond to interrupts may never terminate.
 *
 * @throws SecurityException {@inheritDoc}
 */
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();   // 中断所有线程
        tasks = drainQueue(); // 移除等待队列中的所有任务
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

Understand how shutdownNow() does not accept new tasks or process tasks in the queue when closing the thread pool, and interrupt running tasks. The key lies in three operations:

advanceRunState(STOP)

After setting the thread pool status to STOP:

  • In ThreadPoolExecutor#execute, the thread pool state is STOP and will not receive new tasks.
  • In ThreadPoolExecutor#getTask, the thread pool state is STOP, no matter whether there are unprocessed tasks in the queue, it will no longer be pulled.
  • In ThreadPoolExecutor#runWorker, if the thread pool status is STOP, interrupt() will be executed to set the interrupt status. Whether the task will continue to execute depends on whether the interrupt status is checked in the task.
interruptWorkers

java.util.concurrent.ThreadPoolExecutor#interruptWorkers

/**
 * Interrupts all threads, even if active. Ignores SecurityExceptions
 * (in which case some threads may remain uninterrupted).
 */
private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

java.util.concurrent.ThreadPoolExecutor.Worker#interruptIfStarted

void interruptIfStarted() {
    Thread t;
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { // 注意这里没有获取锁!
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

Compared with interruptIdleWorkers(), interruptWorkers() only needs to verify that getState() >= 0 before interrupting the thread, and the running thread can be forcibly interrupted without acquiring the lock.

  • If the target thread pulls a task in ThreadPoolExecutor#getTask, since the pull task does not need to hold a lock, the target thread will be interrupted and awakened while waiting for the task, and will no longer pull the task after re-spin check.
  • If the target thread executes a task in ThreadPoolExecutor#runWorker, the interruption status will be forcibly set, but whether the task will continue to execute depends on whether the interruption status is checked in the task.
drainQueue

java.util.concurrent.ThreadPoolExecutor#drainQueue

/**
 * Drains the task queue into a new list, normally using
 * drainTo. But if the queue is a DelayQueue or any other kind of
 * queue for which poll or drainTo may fail to remove some
 * elements, it deletes them one by one.
 */
private List<Runnable> drainQueue() {
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    q.drainTo(taskList); // 批量将队列中的任务转移到 taskList
    if (!q.isEmpty()) {
        for (Runnable r : q.toArray(new Runnable[0])) {
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

Pull all unprocessed tasks in the workQueue queue to taskList, and no more tasks are processed.

5. Thread configuration

Reasonably configure the thread pool:

  • CPU-intensive tasks: Configure threads as small as possible, such as configuring N cpu + 1 thread pool to reduce scheduling overhead.
  • IO-intensive tasks: Since threads are not always performing tasks, you should configure as many threads as possible, such as 2*N cpu .
  • Hybrid tasks: As long as the time difference between the execution of these two tasks is not too large, split them into a CPU-intensive task and an IO-intensive task.

"Java Concurrent Programming Practice" puts forward a formula for calculating the number of threads.

definition:

$$ N_{cpu} = CPU 核心数 $$

$$ U_{cpu} = 目标 CPU 利用率, 0 \leqslant U_{cpu} \leqslant 1 $$

$$ \frac{ W }{ C } = 等待时间和计算时间的比例 $$

To make the processor reach the desired utilization rate, the optimal size of the number of threads is equal to:

$$ N_{threads} = N_{cpu} * U_{cpu} * ( 1 + \frac{ W }{ C } ) $$

The number of CPU cores can be obtained through Runtime:

int N_CPU = Runtime.getRuntime().availableProcessors();

6. Summary

Review the internal structure of ThreadPoolExecutor:
线程池

  1. If the currently running threads are less than corePoolSize, new threads will be created to perform tasks even if there are idle threads.
  2. If the running thread is equal to or more than corePoolSize, the task is added to the BlockingQueue.
  3. If the task cannot be added to the BlockingQueue (the queue is full), a new thread is created to process the task.
  4. If creating a new thread will cause the currently running thread to exceed the maximumPoolSize, the task will be rejected.

Author: Sumkor
Link: https://segmentfault.com/a/1190000040009000


Sumkor
148 声望1.3k 粉丝

会写点代码