Notes after reading "The Art of Java Concurrent Programming" - Detailed Explanation of ThreadPoolExecutor (Chapter 10)

Notes after reading "The Art of Java Concurrent Programming" - Detailed Explanation of ThreadPoolExecutor (Chapter 10)

1. Introduction to ThreadPool Executor

The core class of the Executor framework is ThreadPoolExecutor, which is the implementation class of the thread pool and mainly consists of the following four components:

  1. corePool: The size of the core thread pool.
  2. maximumPool: The maximum thread pool size.
  3. BlockingQueue: A work queue used to temporarily save tasks.
  4. RejectedExecutionHandler: When the ThreadPoolExecutor has been closed or the ThreadPoolExecutor has been saturated (the maximum thread pool size has been reached and the work queue is full), the execute()Handler that the method will call.

Through the tool class of the Executor framework Executors, three types of ThreadPoolExecutor can be created:

  1. FixedThreadPool
  2. SingleThreadExecutor
  3. CachedThreadPool

2. Detailed Explanation of FixedThreadPool

FixedThreadPool is known as a thread pool that can reuse a fixed number of threads.

Source code implementation:

public static ExecutorService newFixedThreadPool(int nThreads) {
    
    
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

It can be seen from the source code:

  1. Both corePoolSize and maximumPoolSize of FixedThreadPool are set to the parameters specified when creating FixedThreadPoolnThreads
  2. When the number of threads in the thread pool is greater than corePoolSize, keepAliveTime is the maximum time for redundant idle threads to wait for new tasks . After this time, redundant threads will be terminated. Here keepAliveTime is set to 0L, which means that redundant idle threads will be terminated immediately.

FixedThreadPoolSchematic diagram of the operation of execute()the method:

image-20220118133612324

  1. If the number of currently running threads is less than corePoolSize, new threads are created to perform tasks.
  2. After the thread pool finishes warming up (the number of currently running threads is equal to corePoolSize), add tasks to LinkedBlockingQueue.
  3. After the thread executes the task in 1, it will repeatedly obtain the task from the LinkedBlockingQueue in the loop to execute. FixedThreadPool uses the unbounded queue LinkedBlockingQueue as the work queue of the thread pool (the capacity of the queue is Integer.MAX_VALUE).

Using an unbounded queue as a work queue will have the following effects on the thread pool:

  1. When the number of threads in the thread pool reaches corePoolSize, new tasks will wait in the unbounded queue, so the number of threads in the thread pool will not exceed corePoolSize.
  2. Due to 1, maximumPoolSize will be an invalid parameter when using unbounded queues.
  3. Because of 1 and 2, keepAliveTime will be an invalid parameter when using an unbounded queue.
  4. Due to the use of unbounded queues, the running FixedThreadPool (not executing method shutdown() or shutdownNow()) will not reject tasks (the RejectedExecutionHandler.rejectedExecution method will not be called)

3. Detailed explanation of SingleThreadExecutor

SingleThreadExecutor is an Executor that uses a single worker thread.

Source code implementation:

public static ExecutorService newSingleThreadExecutor() {
    
    
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

It can be seen from the source code:

  1. The corePoolSize and maximumPoolSize of the SingleThreadExecutor are set to 1. Other parameters are the same as FixedThreadPool.
  2. SingleThreadExecutor uses the unbounded queue LinkedBlockingQueue as the work queue of the thread pool (the capacity of the queue is Integer.MAX_VALUE).
  3. SingleThreadExecutor uses an unbounded queue as a work queue, which has the same impact on the thread pool as FixedThreadPool, so I won’t go into details here.

SingleThreadExecutorThe operation schematic diagram:

image-20220118134235094

  1. If the number of currently running threads is less than corePoolSize (that is, there are no running threads in the thread pool), create a new thread to execute the task.
  2. After the thread pool finishes warming up (there is a running thread in the current thread pool), add tasks to LinkedBlockingQueue.
  3. After the thread executes the task in 1, it will repeatedly obtain the task from the LinkedBlockingQueue in an infinite loop to execute.

4. Detailed explanation of CachedThreadPool

CachedThreadPool is a thread pool that creates new threads as needed.

Source code implementation:

public static ExecutorService newCachedThreadPool() {
    
    
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

It can be seen from the source code:

  1. The corePoolSize of the CachedThreadPool is set to 0, that is, the corePool is empty.
  2. maximumPoolSize is set to Integer.MAX_VALUE, that is, maximumPool is unbounded.
  3. Here, keepAliveTime is set to 60L, which means that the idle thread in the CachedThreadPool waits for a new task for a maximum of 60 seconds, and the idle thread will be terminated after more than 60 seconds .
  4. FixedThreadPool and SingleThreadExecutor use the unbounded queue LinkedBlockingQueue as the work queue of the thread pool. CachedThreadPool uses SynchronousQueue without capacity as the work queue of the thread pool , but the maximumPool of CachedThreadPool is unbounded. This means that if the main thread submits tasks faster than the threads in the maximumPool can process tasks, the CachedThreadPool will continue to create new threads. In extreme cases, the CachedThreadPool will exhaust CPU and memory resources due to creating too many threads

CachedThreadPoolThe execution diagram of execute()the method:

image-20220118141748659

  1. First execute SynchronousQueue.offer(Runnable task). If there is an idle thread running in the maximumPool SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS), then the offer operation executed by the main thread is successfully paired with the poll operation executed by the idle thread, and the main thread hands over the task to the idle thread for execution, and the execute() method is executed; otherwise, execute step 2 below.
  2. When the initial maximumPool is empty, or there are currently no idle threads in the maximumPool, no thread will execute SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS). In this case, step 1 will fail. At this point, the CachedThreadPool will create a new thread to execute the task, and the execute() method will be executed.
  3. After the newly created thread in step 2 finishes executing the task, it will execute SynchronousQueue.poll (keepAliveTime, TimeUnit.NANOSECONDS). This poll operation will make the idle thread wait in the SynchronousQueue for a maximum 60of seconds. If the main thread submits a new task within 60 seconds (the main thread executes step 1), then the idle thread will execute the new task submitted by the main thread; otherwise, the idle thread will terminate. Since idle threads that are idle for 60 seconds are terminated, a CachedThreadPool that remains idle for a long time will not use any resources.

SynchronousQueue is a blocking queue with no capacity. Each insert operation must wait for the corresponding remove operation from another thread , and vice versa. CachedThreadPool uses SynchronousQueue to pass tasks submitted by the main thread to idle threads for execution.

CachedThreadPoolThe schematic diagram of the task transfer in the middle is shown in the figure:

image-20220118142137859

insert image description here

Guess you like

Origin blog.csdn.net/qq_45966440/article/details/122772049