"Original" https://www.toutiao.com/i6566022142666736131/
When we use threads to create a thread, it's easy to implement, but there's a problem:
If the number of concurrent threads is large, and each thread is executing a short task, then the frequent creation of threads can greatly reduce the efficiency of the system because it takes time to create threads and destroy threads frequently.
So is there a way to enable a thread to be reused, to perform a task, not to be destroyed, but to continue to perform other tasks?
This can be achieved through the thread pool in Java. Today we will explain in detail the Java thread pool, first we start from the core of the Threadpoolexecutor class method, and then the implementation of the principle, and then give its use example, finally discussed how to reasonably configure the size of the thread pool.
The following is the directory outline for this article:
I. Threadpoolexecutor class in Java
Two. Deep analysis of thread pool implementation principles
Three. Using the example
Four. How to properly configure the size of the thread pool
If there are any shortcomings please understand, and welcome criticism.
I. Threadpoolexecutor class in Java
The Java.uitl.concurrent.ThreadPoolExecutor class is the most core class in the thread pool, so if you want a thorough understanding of the thread pools in Java, you must first understand this class. Let's look at the specific implementation of the Threadpoolexecutor class source.
Four construction methods are provided in the Threadpoolexecutor class:
As you can tell from the code above, Threadpoolexecutor inherits the Abstractexecutorservice class and provides four constructors, in fact, by observing the concrete implementation of each constructor's source code, It is found that the first three constructors are initialized by the fourth constructor that is called.
The following explains the meanings of each parameter in the constructor:
- corepoolsize: The size of the core pool, which is very much related to the implementation principle of the thread pool described later. After the thread pool has been created, by default, there are no threads in the thread pools, instead of waiting for a task to be created to perform the task, unless the prestartallcorethreads () or Prestartcorethread () method is called. As you can see from the names of these 2 methods, the meaning of the pre-created thread is to create a corepoolsize thread or a thread before the task arrives. By default, after a thread pool has been created, the number of threads in the thread pools is 0, and when a task comes, a thread is created to perform the task, and when the number of threads in the thread pool reaches corepoolsize, the incoming task is placed in the cache queue;
- Maximumpoolsize: Thread pool Maximum number of threads, this parameter is also a very important parameter that represents the maximum number of threads that can be created in the thread pool;
- KeepAliveTime: Indicates the maximum length of time a thread will be terminated without a task execution. By default, KeepAliveTime only works if the number of threads in the thread pool is greater than corepoolsize, until the number of threads in the thread pool is not greater than corepoolsize, that is, when the number of threads in the thread pool is greater than corepoolsize. If a thread is idle for KeepAliveTime, it terminates until the number of threads in the thread pool does not exceed corepoolsize. However, if the Allowcorethreadtimeout (Boolean) method is called and the number of threads in the thread pool is not greater than corepoolsize, the KeepAliveTime parameter also works until the number of threads in the thread pool is 0;
- Unit: The time unit of the parameter KeepAliveTime, there are 7 kinds of values, there are 7 kinds of static properties in the Timeunit class:
Timeunit.days; Days
timeunit.hours; Hours
Timeunit.minutes; Minutes
Timeunit.seconds; Seconds
Timeunit.milliseconds; Milliseconds
Timeunit.microseconds; Subtle
Timeunit.nanoseconds; Na-Sec
- WorkQueue: A blocking queue that stores the tasks waiting to be executed, and the choice of this parameter is also important to have a significant impact on the running process of the thread pool, in general, where the blocking queue has the following options:
- Arrayblockingqueue;
- Linkedblockingqueue;
- Synchronousqueue;
Arrayblockingqueue and Priorityblockingqueue use less, generally using linkedblockingqueue and synchronous. The thread pool's queuing policy is related to Blockingqueue.
- Threadfactory: Thread factory, mainly used to create threads;
- Handler: Represents the following four types of values when a policy is rejected when processing a task:
The configuration of the specific parameters is described later in relation to the thread pool.
The code from the Threadpoolexecutor class given above can tell that Threadpoolexecutor inherits Abstractexecutorservice, Let's take a look at the implementation of Abstractexecutorservice:
Abstractexecutorservice is an abstract class that implements the Executorservice interface.
Let's look at the implementation of the Executorservice interface:
and Executorservice inherited the executor interface, we look at the implementation of the Executor interface:
Here, you should understand the relationship between Threadpoolexecutor, Abstractexecutorservice, Executorservice and executor.
Executor is a top-level interface in which only one method execute (Runnable) is declared, the return value is void, the argument is of type Runnable, and is literally understood to be used to perform the task passed in;
Then the Executorservice interface inherits the executor interface and declares some methods: Submit, InvokeAll, Invokeany and shutdown, etc.
Abstract class Abstractexecutorservice implements the Executorservice interface, which basically implements all the methods declared in Executorservice;
Then Threadpoolexecutor inherits the class Abstractexecutorservice.
There are several very important methods in the Threadpoolexecutor class:
- Execute ()
- Submit ()
- Shutdown ()
- Shutdownnow ()
The Execute () method is actually the method declared in executor, which is implemented in Threadpoolexecutor, which is the core method of Threadpoolexecutor, which can be used to submit a task to the thread pool. To the thread pool to execute.
The Submit () method is a method declared in Executorservice, which has a specific implementation in Abstractexecutorservice and is not overridden in threadpoolexecutor. This method is also used to submit a task to the thread pool, but unlike the Execute () method, it can return the result of the task execution and see the implementation of the Submit () method, and it will actually be called the Execute () method. It only uses the future to get the results of the task execution (future related content will be described in the next article).
Shutdown () and Shutdownnow () are used to close the thread pool.
There are many other ways to do it:
For example: Getqueue (), Getpoolsize (), Getactivecount (), Getcompletedtaskcount () and other methods to get the related properties of the thread pool, interested friends can consult the API themselves.
two. Deep analysis of thread pool implementation principles
In the previous section, we introduced the threadpoolexecutor from the macro, the following we will be in-depth analysis of the thread pool implementation of the principle, from the following aspects of the explanation:
1. Thread pool status
2. Implementation of the mandate
3. Thread initialization in a thread pool
4. Task cache queue and queuing policy
5. Task Rejection Policy
6. Shutdown of the thread pool
7. Dynamic adjustment of thread pool capacity
1. Thread pool status
A volatile variable is defined in Threadpoolexecutor, and several static final variables are defined to represent each state of the thread pool:
- volatile int runstate;
- Static Final int RUNNING = 0;
- Static Final int SHUTDOWN = 1;
- Static Final int STOP = 2;
- Static Final int TERMINATED = 3;
Runstate represents the state of the current thread pool, which is a volatile variable used to ensure the visibility of the threads;
The following static final variables represent several possible values for runstate.
When the thread pool is created, it is initially in the running state;
If the shutdown () method is called, then the thread pool is in the shutdown state, and the line pool is not able to accept the new task, and it waits for all tasks to complete;
If the Shutdownnow () method is called, the thread pool is in the stop state, and this time, the thread pools are unable to accept new tasks, and will attempt to terminate the task being performed;
When the thread pool is in the shutdown or stop state, and all worker threads have been destroyed, the task cache queue has been emptied or the execution finishes, the thread pool is set to the terminated state.
2. Implementation of the mandate
Before we get to the end of the process of handing over tasks to the thread pool, let's take a look at some of the other more important member variables in the Threadpoolexecutor class:
The role of each variable has been marked out, here to focus on explaining the Corepoolsize, Maximumpoolsize, largestpoolsize three variables.
Corepoolsize in many places is translated into the core pool size, in fact, I understand this is the size of the thread pool. To give a simple example:
If there is a factory, there are 10 workers in the factory, each worker can only do one task at the same time.
So as long as 10 workers are idle, the task is assigned to the idle workers;
When 10 workers have a task to do, if the task is still in the queue to wait for the task;
If the number of new tasks is growing much faster than the workers do, then the factory supervisor may want to remedy the situation, such as re-recruit 4 temporary workers to come in;
The task is then assigned to the 4 temporary workers;
If it is not enough to say that 14 workers are doing the task, the factory supervisor may want to consider no longer accepting new tasks or abandoning some of the previous tasks.
When one of the 14 workers is free, and the new task grows at a slower pace, the factory manager may consider quitting 4 temporary workers, keeping the original 10, after all, to ask for extra workers to spend money.
The corepoolsize in this example is 10, and Maximumpoolsize is 14 (10+4).
That is, Corepoolsize is the size of the thread pool, and maximumpoolsize, in my opinion, is a remedial measure of the thread pool, that is, when the task volume is suddenly too large.
However, to facilitate understanding, Corepoolsize is translated into core pool size later in this article.
Largestpoolsize is just a variable used to record the maximum number of threads that have ever been in a thread pool, with no relation to the capacity of the thread pools.
Let's take a look at the process that the task went through from submission to final execution.
In the Threadpoolexecutor class, the most core task-submission method is the Execute () method, although it is possible to submit a task by submitting it, but actually the execute () method is ultimately called in the Submit method, So we just need to study the implementation principle of the Execute () method:
The above code may not seem so easy to understand, let's say one sentence:
First, determine whether the committed task command is NULL, if NULL, throws a null pointer exception;
Then this sentence, this sentence should be well understood:
if (poolsize >= corepoolsize | |!addifundercorepoolsize (command))
Because of the or conditional operator, the value of the first half is evaluated first, and if the current number of threads in the thread pool is not less than the core pool size, then the following if statement block is entered directly.
If the current number of threads in the thread pool is less than the core pool size, then the second half is executed
Addifundercorepoolsize (command)
If this method returns False when the addifundercorepoolsize is executed, the following block of if statements is executed, otherwise the entire method is executed directly.
If you finish Addifundercorepoolsize, this method returns False, and then it then determines:
if (Runstate = = RUNNING && workqueue.offer (command))
If the current thread pool is in the running state, the task is placed in the task cache queue, if the current thread pool is not in the running state, or if the task fails to put in the cache queue:
Addifundermaximumpoolsize (command)
If the Execute Addifundermaximumpoolsize method fails, The Reject () method is executed to perform the task reject processing.
Back to Front:
if (Runstate = = RUNNING && workqueue.offer (command))
The execution of this sentence, if the current thread pool is in the running state and the task is placed in the task cache queue succeeds, continue to judge:
if (runstate! = RUNNING | | poolsize = 0)
This judgment is intended to prevent a contingency in which other threads suddenly call the shutdown or Shutdownnow method to shut down the thread pool while adding this task into the task cache queue. If this is the case, execute:
ensurequeuedtaskhandled (command)
For emergency processing, it can be seen from the name that the task being added to the task cache queue is processed.
Let's take a look at the implementation of the 2 key methods: Addifundercorepoolsize and Addifundermaximumpoolsize:
This is the concrete implementation of the Addifundercorepoolsize method, which can be seen from the name as a way of doing it when it is below the core to eat the hours. Here is a concrete implementation, first get to the lock, because this place involves the change of the thread pool state, first through the IF statement to determine whether the number of threads in the current thread pool is less than the core pool size, a friend may have doubts: the previous in the Execute () method is not already judged, Only the thread pool executes the Addifundercorepoolsize method when the number of front-line threads is smaller than the core pool size, why should this place continue to judge? The reason is very simple, the previous judgment process is not locked, so it may be in the Execute method to judge the time poolsize less than corepoolsize, and after the judgment, in other threads and then to the thread pool to submit the task, May cause poolsize not less than corepoolsize, so need to continue to judge in this place. It then determines whether the thread pool's state is running, which is also simple because it is possible to invoke the shutdown or Shutdownnow method in other threads. Then it's execution.
t = Addthread (firsttask);
This method is also very critical, the arguments passed in are the submitted task, and the return value is the thread type. It then determines whether T is empty, and Null indicates that the creation thread failed (that is, poolsize>=corepoolsize or runstate is not equal to running), otherwise the T.start () method is called to start the thread.
Let's take a look at the implementation of the Addthread method:
In the Addthread method, a Worker object is first created with the submitted task, and then the thread factory threadfactory creates a new thread T and assigns a reference to the thread T to the member variable thread of the Worker object. The worker object is then added to the working set by Workers.add (W).
Let's consider the implementation of the worker class:
It actually implements the Runnable interface, so the thread t = Threadfactory.newthread (w) above, the effect is basically the same as the following sentence:
Thread T = new thread (w);
Equivalent to a runnable task that executes this runnable in a thread t.
Now that the worker implements the Runnable interface, the natural core approach is the run () method:
As can be seen from the implementation of the Run method, it first executes the task Firsttask through the constructor, after the call Runtask () executes Firsttask, in the while loop through the Gettask () to fetch new tasks to execute, then where to fetch? Naturally, it is taken from the task cache queue, Gettask is the method in the Threadpoolexecutor class, not the method in the worker class, the following is the implementation of the Gettask method:
In Gettask, the current thread pool state is judged first, and if Runstate is greater than shutdown (that is, stop or terminated), NULL is returned directly.
If Runstate is shutdown or running, the task is taken from the task cache queue.
If the current thread pool has more threads than the core pool size corepoolsize or allows idle time to be set for threads in the core pool, call poll (Time,timeunit) to fetch the task, which waits for a certain amount of time and returns null if the task is not taken.
Then determine if the task R is null, or NULL, by calling the Workercanexit () method to determine if the current worker can exit, let's take a look at the implementation of Workercanexit ():
That is, if the thread pool is in the stop state, or if the task queue is empty or the core pool thread is allowed to set idle time and the number of threads is greater than 1 o'clock, worker exits are allowed. If the worker is allowed to exit, call Interruptidleworkers () to break the idle worker, and let's take a look at the implementation of Interruptidleworkers ():
As you can see from the implementation, it actually calls the worker's Interruptifidle () method, in the worker's Interruptifidle () method:
Here is a very ingenious design, if we are to design a thread pool, there may be a task to dispatch threads, when the thread is found idle, take a task from the task cache queue to the idle thread execution. However, this is not the case in this way, because it will be added to the task of assigning threads to manage, invisibly increase the difficulty and complexity, here directly to the completion of the task of the thread to the task cache queue to take the task to execute.
We look at the realization of the Addifundermaximumpoolsize method, the realization of this method and the realization of the Addifundercorepoolsize method is very similar to the idea, The only difference is that the Addifundermaximumpoolsize method is performed when the number of threads in the thread pool reaches the core pool size and adds a task to the task queue:
See no, in fact it and addifundercorepoolsize method implementation basically exactly the same, only if the sentence judgment condition Poolsize < maximumpoolsize is different.
Here, most of your friends should have a basic understanding of the task being submitted to the thread pool and the entire process being performed, as summarized below:
1) First of all, to understand the meaning of corepoolsize and maximumpoolsize;
2) Second, to know what the worker is used to play a role;
3) to know the processing strategy after the task is submitted to the thread pool, here is a summary of the main 4 points:
- If the number of threads in the current thread pool is less than corepoolsize, then each task will create a thread to perform the task;
- If the number of threads in the current thread pool is >=corepoolsize, each task will attempt to add it to the task cache queue, and if added succeeds, the task waits for the idle thread to take it out, or if the add fails (typically, the task cache queue is full). Will attempt to create a new thread to perform this task;
- If the number of threads in the current thread pool reaches maximumpoolsize, a task rejection policy is taken to handle it;
- If the number of threads in the thread pool is greater than corepoolsize, if a thread is idle for more than KeepAliveTime, the thread is terminated until the number of threads in the thread pool is not greater than corepoolsize, and if the threads in the core pool are allowed to set the time to live, If the thread in the core pool is idle longer than KeepAliveTime, the thread is terminated.
3. Thread initialization in a thread pool
By default, after the thread pool is created, there are no threads in the threads pools, and threads are not created until the task is committed.
In practice, if you need to create threads immediately after the thread pool is created, there are two ways to do this:
- Prestartcorethread (): Initializes a core thread;
- Prestartallcorethreads (): Initialize all core threads
Here are the implementations of these 2 methods:
Note that the argument passed in is null, according to the analysis of the 2nd section, if the argument passed in is null, then the last execution thread will block the Gettask method.
R = Workqueue.take ();
That is, wait for tasks in the task queue.
4. Task cache queue and queuing policy
In the previous we mentioned the task cache queue, Workqueue, which is used to hold tasks waiting to be executed.
The type of workqueue is blockingqueue<runnable> The following three types are usually available:
1) Arrayblockingqueue: An array-based FIFO queue, which must specify a size when created;
2) Linkedblockingqueue: A list-based FIFO queue, which defaults to Integer.max_value if this queue size is not specified when it is created;
3) Synchronousqueue: This queue is special, it does not save the submitted task, but will create a new thread directly to perform the new task.
5. Task Rejection Policy
When the thread pool's task cache queue is full and the number of threads in the thread pools reaches maximumpoolsize, a task rejection policy is taken if there is a task coming, typically with the following four strategies:
- Threadpoolexecutor.abortpolicy: Discards the task and throws a rejectedexecutionexception exception.
- Threadpoolexecutor.discardpolicy: Also discards the task, but does not throw an exception.
- Threadpoolexecutor.discardoldestpolicy: Discards the first task in the queue and then tries to perform the task again (repeat this process)
- Threadpoolexecutor.callerrunspolicy: The task is handled by the calling thread
6. Shutdown of the thread pool
Threadpoolexecutor provides two methods for closing the thread pool, respectively, shutdown () and Shutdownnow (), Where:
- Shutdown (): Does not terminate the thread pool immediately, but waits until all tasks in the task cache queue have been executed before terminating, but will no longer accept new tasks
- Shutdownnow (): Terminates the thread pool immediately, attempts to break the task in progress, and empties the task cache queue, returning tasks that have not yet been performed
7. Dynamic adjustment of thread pool capacity
Threadpoolexecutor provides a way to dynamically adjust the size of the thread pool capacity: Setcorepoolsize () and Setmaximumpoolsize (),
- Setcorepoolsize: Setting the core pool size
- Setmaximumpoolsize: Set the maximum number of threads the thread pool can create
When the above parameters grow from small to large, threadpoolexecutor threads are assigned, and it is possible to create new threads immediately to perform tasks.
three. Using the example
We discussed the implementation principle of the thread pool, and this section looks at its specific use:
Execution Result:
As you can see from the execution results, when the number of threads in the thread pool is greater than 5 o'clock, the task is placed in the task cache queue and a new thread is created when the task cache queue is full. If you change the for loop to perform 20 tasks in the above program, the task reject exception will be thrown.
In Java doc, however, we do not advocate using threadpoolexecutor directly, but instead use several static methods provided in the Executors class to create the thread pool:
- Executors.newcachedthreadpool (); Create a buffer pool with a buffer pool capacity of integer.max_value
- Executors.newsinglethreadexecutor (); Create a buffer pool with a capacity of 1
- Executors.newfixedthreadpool (int); Create a buffer pool of fixed capacity size
The following is a concrete implementation of these three static methods;
They actually call the threadpoolexecutor from their specific implementations, except that the parameters are already configured.
Newfixedthreadpool creates a thread pool corepoolsize and maximumpoolsize values are equal, it uses the Linkedblockingqueue;
Newsinglethreadexecutor the Corepoolsize and Maximumpoolsize are set to 1, also use the linkedblockingqueue;
Newcachedthreadpool set Corepoolsize to 0, set Maximumpoolsize to Integer.max_value, use Synchronousqueue, that is, the task is to create a thread to run, When the thread is idle for more than 60 seconds, the thread is destroyed.
In practice, if the three static methods provided by executors can meet the requirements, try to use the three methods it provides, because it is a bit cumbersome to manually configure the Threadpoolexecutor parameters, depending on the type and number of actual tasks.
In addition, if the threadpoolexecutor is not up to the requirement, you can override it by inheriting the Threadpoolexecutor class yourself.
four. How to properly configure the size of the thread pool
This section discusses a more important topic: How to properly configure the thread pool size for informational purposes only.
It is generally necessary to configure the thread pool size according to the type of task:
For CPU-intensive tasks, you need to squeeze the CPU as much as possible, and the reference value can be set to Ncpu+1
If it is an IO-intensive task, the reference value can be set to 2*NCPU
Of course, this is only a reference value, the specific settings also need to adjust according to the actual situation, such as the thread pool size can be set to reference values, and then observe the task run and system load, resource utilization to make appropriate adjustments.
"Go" Java Learning---in-depth understanding of thread pools