Java concurrent thread pool
The role of thread pools
Pooling technology is a very common computer technology, mainly for reuse and improve performance, such as memory pooling, connection pooling, object pooling, etc. Thread pools are no exception. Their main functions are as follows:
-
Improved performance: Thread reuse in thread pools can significantly reduce the overhead associated with frequent creation and destruction of threads.
-
Reuse and management: Facilitate the management and reuse of threads in the pool, avoiding the creation of a large number of threads in a production environment.
-
Decoupling: Only the interface of the submitted task is exposed to decouple the creation and destruction of the thread pool from the business.
There are basic thread pool classes, there are thread pool factories, but most importantly, ThreaPoolExecutor, which is one of the most frequently asked questions in interviews. This article focuses on ThreaPoolExecutor.
Description of thread pool parameters
There are many constructors defined in ThreaPoolExecutor, but the internal implementation calls the most basic, full-argument constructor, which is defined as follows:
ThreaPoolExecutor(
int corePoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
)
Copy the code
The ThreaPoolExecutor parameter has the following meanings
- CorePoolSize: Number of core threads in the thread pool.
- MaximumPoolSize: the maximum number of threads in the thread pool.
- KeepAliveTime: The lifetime of extra idle threads when the number of thread pools exceeds corePoolSize. How long it takes for idle threads exceeding coolPoolSize to be destroyed.
- Unit: unit of keepAliveTime. The value can be hour, minute, or second.
- WorkQueue: A task queue storing submitted tasks that have not yet been executed.
- ThreadFactory: a threadFactory used to create threads, usually using the default.
- Handler: Rejection policy for rejecting a task if the thread pool cannot handle it.
The workQueue, threadFactory, and handler parameters above are complex and need to be described separately. WorkQueue was described in another article, “Java Concurrency blocking queues”. Let’s focus on ThreadFactory and RejectedExecutionHandler
1. ThreadFactory: ThreadFactory
Threads in the Thread pool are created by the Thread factory defined by TrheadFactory, which is an interface with only the Thread newThread(Runnable R) method used to create threads. Although this parameter may not be specified when ThreadPoolExecutor is created, the Alibaba coding specification recommends that this parameter be specified for the following benefits:
- Track when and how many threads are created in the thread pool.
- You can customize the name, group, and priority of the thread pool.
- Set other states of the thread, such as daemons.
2. Reject policy: RejectedExecutionHandler
When the number of threads in the thread pool reaches maxPoolSize, new tasks are rejected. The JDK defines four rejection policies:
- AbortPolicy This policy directly throws an exception
- CallerRunsPolicy caller thread processing tasks, this strategy is not really a discarded tasks, will make the current thread is abandoned to perform the task, since there is only a single thread, all tasks will be executed serially.
- DiscardOldestPolicy Discards the oldest request, i.e., the task at the head of the queue that is about to be executed, and attempts to resubmit the current task.
- DiscardPolicy Silently discards tasks that cannot be processed.
The four rejection policies inherit the RejectedExecutionHandler interface and implement the rejectedExecution(Runnable r, ThreadPoolExecutor Executor) method of the interface. If none of the previous four rejection policies can meet your requirements, you can customize the RejectedExecutionHandler interface and implement the RejectedExecutionHandler method.
Scheduling logic for thread pools
ThreaPoolExecutor’s logic for handling submitted tasks is shown below,
1. When submitting tasks:
- If the number of threads in the thread pool is less than corePoolSize (regardless of whether there are idle threads), a new thread (called the core thread) is created to handle it.
- If the number of threads in the thread pool is greater than or corePoolSize, the newly submitted task is placed on a waiting queue for scheduling.
- If the wait queue is full and the number of threads in the thread pool is less than maxPoolSize, new threads are created to process the task.
- If the queue is full and the number of threads has reached the upper limit, a rejection policy is used.
2. During the task:
- When tasks in the queue have been completed and some threads are idle, non-core threads will destroy themselves within keepAliveTime after they are idle.
- Whether the idle core thread exits depends on another thread pool parameter, allowCoreThreadTimeOut. When set to true, timeout exits even for core threads.
The thread pool life cycle
Thread pools, like threads, have a life cycle. They are in five TERMINATED states: RUNNING, SHUTDOWN, STOP, TIDYING and TERMINATED. The transitions are irreversible.
1. RUNNING
This state is the working state of the thread pool and is able to accept new tasks and process accepted tasks. The initial state of the thread pool, which is processed after the thread is successfully created.
2. SHUTDOWN
Closed state, the thread pool can no longer accept new tasks, but can continue to process tasks submitted to the thread pool. If the thread is RUNNING, call shutdown() to enter the RUNNING state.
3. STOP
In the stopped state, the thread pool accepts no new tasks, processes no tasks in the blocking queue, and interrupts the thread executing the task. When a thread is in the RUNNING or SHUTDOWN state, use the shutdownNow() method to enter the state.
4. TIDYING
All tasks are destroyed, and the workCount is 0, which automatically transitions from RUNNING or STOP to TIDYING. The ternimated() method of the ThreadPoolExecutor class is empty and can be overridden if you want to process the thread pool when it becomes TIDYING.
When the thread pool is SHUTDOWN and the blocking queue is empty and the execution task is empty, it switches to TIDYING state. A thread pool in the STOP state transitions to the TIDYING state when the task is empty.
5. TERMINATED
End state, the final state of the thread pool in which no further operations will be performed. The thread pool is in this state after executing the terminated() method.
Four thread pools
After learning the basics of ThreaPoolExecutor, take a look at the four thread pool factory methods defined by the JDK for developers under Executors. They actually call ThreaPoolExecutor internally, but using different parameters.
NewFixedThreadPool () : this method returns a fixed number of threads in the thread pool. If there are any free threads in the thread pool, the task will be executed immediately. If there are no new threads in the thread pool, the new task will be cached in a task queue.
public static ExecutorService newFixedThreadPool(int nthread) {
return new ThreadPoolExecutor(nthread,
nthread,
0L,
TimeUnit.MILLSECCONDS,
new LikedBlockingQueue<Runnable>())
}
Copy the code
NewSingleThreadExecutor () method: This method returns a thread pool with only one thread, and if additional tasks are submitted to the thread pool, they are submitted to the task queue. It creates the thread pool code as follows:
public static ExecutorService newSingleThreadExecutor(a) {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1.1.0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Copy the code
NewCachedThreadPool () method: This method returns a pool with an adjusted number of threads, a core of zero, a total number of integer. MAX_VALUE, and a SynchronousQueue so that tasks cannot be submitted to the queue even if the threads are full.
public static ExecutorService newCachedThreadPool(a) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
Copy the code
NewScheduledThreadPool () : This method has a pool of threads of fixed length and executes tasks on a deferred or timed basis. Its queue uses DelayedWorkQueue, so the task must inherit the Delay interface.
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
Copy the code