JDK8 ThreadPoolExcutor JAVA DOC

Number of core threads and maximum threads

ThreadPoolExecutor adjusts the size of the thread pool based on the number of core threads and the maximum number of threads you set.

  • When you call threadPoolExecutor. Execute (Runnable), if the count is less than the core thread pool threads and threadPoolExecutor will create a new thread, even though the other core thread is free at this time.

Here’s an example:

  1. You set the number of core threads =5 and the current number of core threads =0.
  2. At this point you call threadPoolExecutor. Execute (Runnable) to submit A task A thread pool.
  3. The Thread pool receives task A and the current core Thread count is 0<5, so Thread 1 is created by the Thread pool.
  4. Thread 1 completes task A soon, and now Thread 1 is idle.
  5. And then you call threadPoolExecutor. Execute (Runnable) to submit a task B to the thread pool.
  6. The Thread pool receives task B, and the current core Thread count is 1<5, so the pool creates Thread 2.
  • If the number of threads in the pool is greater than the number of core threads but less than the maximum number of threads, threadPoolExecutor will only create new threads if the task queue is full.
  • When you set the number of core threads to equal the maximum number of threads, you create a fixed-length thread pool.
  • When you set the maximum number of threads in the thread pool to integer.max_value, you are setting the thread pool to handle an unlimited number of concurrent tasks.
  • Normally, the core thread count and the maximum thread count are set by the constructor, but they can also be set dynamically by Setter methods.

Create thread pools on request

By default, core threads are created only when a task is submitted to threadPoolExecutor, but they can also be created dynamically by calling the following methods:

  1. PrestartCoreThread () creates a core thread
  2. PrestartAllCoreThreads () creates all core threads

For example, if you initialize a thread pool with a non-empty queue of tasks (there are already some tasks in the queue), you can use the above method to pre-initialize the threads in the thread pool

Thread creation strategy

Threads are created using ThreadFactory. If there is no specified custom ThreadFactory, default Executors. DefaultThreadFactory (), so that all the threads belonging to the same ThreadGroup, and all threads have the same priority, and is not a daemon thread. If you provide a custom ThreadFactory, you can change the thread name, thread group, priority, and daemon flag. If ThreadFacotry fails to create the thread and returns null, threadPoolExecutor does not report an error, but does not execute the task.

I don’t quite understand the following paragraph in English. Threads should possess the “modifyThread” RuntimePermission. If worker Threads or other Threads using the pool do not possess this permission, service may be degraded: configuration changes may not take effect in a timely manner, and a shutdown pool may remain in a state in which termination is possible but not completed.

For the moment, translation:

The thread must handle the “modifyThread” RuntimePermission. If the worker thread or other thread in the pool cannot handle this permission, the service may be degraded: configuration changes may not be processed in a timely manner, and a thread pool may remain closed.

Thread lifetime

If the number of threads in the pool is greater than the number of core threads, the excess threads will be destroyed if their idle time is greater than keepAliveTime. This is actually saving resources under low system pressure. If a new task is added to the thread pool, the new thread will be created again. This parameter can also be set dynamically through setter methods. If you set keepAliveTime to long. MAX_VALUE, you disable the subparameter. By default, a thread expiration time only takes effect if the number of threads is greater than the number of core threads. But allowCoreThreadTimeOut(Boolean) can also set keepAliveTime to be valid for the number of core threads.

Task queuing strategy

Any blocking queue can be used to hold tasks submitted to the thread pool. The effect of queue usage is related to thread size, as shown below:

  • If the number of threads in the pool is less than the number of core threads, threadPoolExecutor creates a new thread.
  • If the number of threads in the pool >= the number of core threads, threadPoolExecutor drops the task to the task queue.
  • If the queue is full, threadPoolExecutor creates a new thread and the task is rejected unless the number of pool threads = the maximum number of threads. There are three standard task queuing strategies, as shown below:
  1. It just changed hands. This strategy is to simply give the task to the thread pool instead of storing it in the queue. For example, SynchronousQueue, a queuing strategy is suitable for simultaneous processing of several interdependent tasks. The direct pass-through strategy usually requires an unbounded maximum number of threads to ensure that tasks are not discarded because there are cases where the task can be submitted faster than it can be processed.
  2. Unbounded queue. With unbounded queues, if the core thread is always working, new tasks are always in the queue, the number of threads in the pool is never greater than the number of core threads, and the maximum number of threads does not matter. This strategy works well in scenarios where each task has no dependencies on the other. For example, a Tomcat-based Java Web service receives a sudden increase in requests and uses queues to buffer them. But with unbounded queues, too many tasks can overwhelm memory.
  3. Bounded queues. Using a finite maximum number of threads with bounded queues does not run out of system resources, but is more difficult to control and debug. There is a tradeoff between the queue length and the maximum number of threads. Using too long queues and too few threads can reduce CPU utilization, system resource utilization, and context switching, but can artificially reduce throughput. If the task blocks frequently, the number of threads may still exceed the legal value. Smaller queue sizes generally require larger thread pools, resulting in higher CPU utilization, but also higher thread scheduling overhead, which also reduces throughput.

Task discarding Policy

New tasks are rejected if the thread pool is closed, or if the thread pool is using a limited maximum number of threads or a bounded queue and is already full. In both cases, the execute method will be called RejectedExecutionHandler. RejectedExecution (Runnable, ThreadPoolExecutor) to handle the rejected the request, the following four processors have been defined in advance:

  1. ThreadPoolExecutor. AbortPolicy throws a RejectedExecutionException anomalies
  2. CallerRunsPolicy ThreadPoolExecutor. Let the caller to perform a task
  3. ThreadPoolExecutor DiscardPolicy do nothing, equivalent to just throw away the task
  4. ThreadPoolExecutor. DiscardOldestPolicy first task from the task queue discarded, put the task in a queue, if once again into the queue also fail, then cycle again.

RejectedExecutionHandler Can be customized. You can design a RejectedExecutionHandler for a specific scenario

Hook method

A Hook is a method that a class reserves several methods for its subclasses to override, so that the subclasses can do some custom operations. It can be understood that the parent class is a curtain rod, and the subclass is the curtain. I have left the hook on the curtain rod for you, and you can use it as long as you hang the ring on the curtain

ThreadPoolExecutor provides beforeExecute(Thread, Runnable) and afterExecute(Runnable, Throwable) methods that can be overridden by subclasses. These two methods are used to do some pre-processing, such as initializing the TheadLocal variable, doing some statistics, and logging. The terminated() method can also be overridden to do something when ThreadPoolExecutor is terminated. If a hook or callback method throws an exception, the internal worker thread may fail and terminate abruptly.