12. ThreadPoolExecutor thread pool Principle and Its execute method, thread pool executesubmit
Jdk1.7.0 _ 79
Most people may use the thread pool, and they also know why. This is nothing more than the asynchronous execution of tasks, and the unified management of threads. Most people may only know how to get a thread from the thread pool. If I need a thread to execute a task, I will throw the task to the thread pool, if there are Idle threads in the thread pool, the thread will be executed, and if there is no idle thread, it will wait. In fact, the execution principle of the thread pool is far more than that simple.
ThreadPoolExecutor, a thread pool class, is provided in Java concurrent packages. In fact, we may use the thread pools provided by the Executors factory class: newFixedThreadPool, newSingleThreadPool, and newCachedThreadPool, these three thread pools are not subclasses of ThreadPoolExecutor. For the relationship between these threads, we will first look at ThreadPoolExecutor, and view the source code to find a total of four constructor methods.
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
First, we will understand the execution principle of ThreadPoolExecutor in the thread pool from these parameters.
CorePoolSize: Number of threads in the Core Thread Pool
MaximumPoolSize: Maximum number of threads in the thread pool
KeepAliveTime: specifies the duration of thread activity persistence. It indicates the time when the worker thread in the thread pool remains alive after idle.
Unit: the unit of the thread activity holding time.
WorkQueue: indicates the blocking queue used by the task queue.
CorePoolSize and maximumPoolSize both specify the number of threads in the thread pool. It seems that when a thread pool is used at ordinary times, you only need to pass a parameter of the thread pool size to create a thread pool, java provides some common thread pool classes, such as newFixedThreadPool, newSingleThreadExecutor, and newCachedThreadPool, of course, if we want to create a custom thread pool, we have to "Configure" some parameters related to the thread pool.
When a task is handed over to the thread pool for processing, the execution principle of the thread pool is shown in reference to the Java concurrent programming art.
① First, it will determine whether there are threads in the Core Thread Pool that can be executed. If there are Idle threads, it will create a thread to execute the task.
② When no thread can be executed in the Core Thread Pool, the task is thrown into the task queue.
③ If the task queue (bounded) is full but the number of running threads is smaller than the maximum number of thread pools, a new thread is created to execute the task, however, if the number of running threads has reached the maximum number of thread pools, a thread cannot be created to execute tasks.
In fact, the thread pool not only simply throws tasks to the thread pool, but also executes the tasks when there are no threads.
To consolidate the thread pool principles, let's take a look at the three commonly used thread pools mentioned above:
Executors. newFixedThreadPool: Creates a thread pool with a fixed number of threads.
// Executors#newFixedThreadPoolpublic static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());}
We can see that the ThreadPoolExecutor class is called in newFixedThreadPool, And the passed parameter corePoolSize = maximumPoolSize = nThread. Review the execution principle of the thread pool. When a task is submitted to the thread pool, the system first checks whether there are Idle threads in the core thread pool, and then creates a thread, if not, the task is placed in the task queue (the bounded blocking queue Queue). If the task queue is full, for newFixedThreadPool, the maximum number of thread pools = the number of Core Thread pools. When the task queue is full, new threads cannot be created to execute tasks.
Executors. newSingleThreadExecutor: Creates a thread pool that contains only one thread.
//Executors# newSingleThreadExecutorpublic static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegateExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));}
The thread pool with only one thread seems a bit strange, and does not directly return ThreadPoolExecutor, or even directly pass the thread pool number 1 to newFixedThreadPool to return. It indicates that the thread pool containing only one thread is not as simple as containing only one thread. Written in the source code comment: Create a thread pool with only one worker thread to operate on an unbounded Queue (if the execution of the front node is terminated, A new thread will continue to execute the successor node thread) the task can continue to execute, unlike newFixedThreadPool (1) there will be no additional thread to re-execute the successor node. That is to say, newSingleThreadExecutor only has one thread to execute from start to end, which is the same as newFixedThreadPool. However, if the end of the thread ends, newSingleThreadExecutor will re-create a new thread to continue executing the threads in the task queue, newFixedThreaPool does not.
Executors. newCachedThreadPool: Creates a thread pool for a new thread as needed.
//Executors#newCachedThreadPoolpublic static ExecutorService newCachedThreadPool() { return new ThreadPooExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());}
NewCachedThread returns ThreadPoolExecutor, whose core thread pool corePoolSize = 0 and maximumPoolSize = Integer. MAX_VALUE, which means that when a task is submitted to the newCachedThread thread pool, the task is directly put into the SynchronousQueue task queue, and maximumPool obtains the task from the task queue. Note that SynchronousQueue is a non-capacity queue. That is to say, each queue operation must wait for the corresponding queue operation of another thread. If the main thread submits a task at a higher speed than the thread processing speed in maximumPool, newCachedThreadPool will constantly create threads. It is not a good thing to have too many threads. It will seriously exhaust CPU and memory resources.
Topic: newFixedThreadPool, newSingleThreadExecutor, and newCachedThreadPool both directly or indirectly call ThreadPoolExecutor. Why are they not directly subclass but instantiated through Executors? This is the static factory method used.,InJava. util. CoThe nnections interface also uses the static factory method to create related classes..This has many benefits.The static factory method is used to generate objects. It does not matter what objects are generated, as long as the original return type or the original return type is returned.Sub-types are acceptable.,LowerNumber and difficulty of using APIs,InImpactJavaArticle 1 is the static factory method.
Return to ThreadPoolExecutor. First, let's look at its inheritance relationship:
ThreadPoolExecutor, whose top-level parent class is the Executor interface, only contains a method called execute, which is the "execution" of the thread pool ".
//Executor#executepublic interface Executor { void execute(Runnable command);}
Executor # execute is implemented in ThreadPoolExecutor:
//ThreadPoolExecutor#executepublic void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); …}
The definition of the ctl variable of Alibaba Cloud is as follows:
private final AtomicInteger ctl = new AtlmicInteger(ctlOf(RUNNING, 0));
Why is this variable used? It works a bit like in. the read/write locks mentioned in ReadWriteLock interface and its implementation ReentrantReadWriteLock have two synchronization statuses: read and write, while AQS only provides one int variable of state, in this case, the 16-Bit High state is expressed as the Read state, and the 16-bit low state is expressed as the write state. The clt here is also used, which represents two concepts:
Int variables have a total of 32 bits, and the five State runstates of the thread pool need at least 3 bits, so the workCount can only have 29 BITs, therefore, the Code specifies that the maximum number of valid threads in the thread pool is 229-1.
// ThreadPoolExecutorprivate static final int COUNT_BITS = Integer. SIZE-3; // 32-3 = 29. The number of threads occupies the private static final int CAPACITY = (1 <COUNT_BITS)-1; // 29-bit low indicates the maximum number of threads, 229-1 // five thread pool States private static final int RUNNING =-1 <COUNT_BITS; /The int type variable is 3-bit (including the symbol bit). Table RUNINGprivate static final int SHUTDOWN = 0 <COUNT_BITS; // high 3-bit 000 private static final int STOP = 1 <COUNT_BITS; // high 3-bit 001 private static final int TIDYING = 2 <COUNT_BITS; // high 3-bit 010 private static final int TERMINATED = 3 <COUNT_BITS; // high 3-bit 011
Return to ThreadPoolExecutor # execute method again:
1 // ThreadPoolExecutor # execute 2 public void execute (Runnable command) {3 if (command = null) 4 throw new NullPointerException (); 5 int c = ctl. get (); // it can obtain the number of valid threads and the status of the thread pool 6/* 1. obtain whether the number of currently running threads is smaller than the Core Thread Pool. if yes, a new thread execution task is created. Otherwise, the task is put into the task queue */7 if (workerCountOf (c) <corePoolSize) {8 if (addWorker (command, tre) // create a worker thread in addWorker to execute the task 9 return; 10 c = ctl. get (); 11} 12/* 2. all threads in the current Core Thread Pool are running workerCountOf (c)> = CorePoolSize, so put the thread in the task queue */13 if (isRunning (c) & workQueue. offer (command) {// check whether the thread pool is running and whether the task is successfully inserted into the task queue. 14 int recheck = ctl. get (); 15 if (! IsRunning (recheck) & remove (command) // check whether the thread pool is in the running state. If not, leave the previous task as 16 reject (command ); // throw a RejectedExceptionException 17 else if (workerCountOf (recheck) = 0) 18 addWorker (null, false); 19} 20/* 3. failed to insert the queue, and the current number of threads is smaller than the maximum number of thread pools. In this case, a new thread is created to execute the task. if creation fails, an exception is thrown */21 else if (! AddWorker (command, false) {22 reject (command); // throw RejectedExceptionException 23} 24}
The code above comments 7th lines to determine whether there are Idle threads in the current Core Thread Pool. If yes, the addWorker method is used to create a worker thread to execute the task. The addWorker method is long and filtering out important code for parsing.
1 // ThreadPoolExecutor # addWorker 2 private boolean addWorker (Runnable firstTask, boolean core) {3/* first checks whether the thread pool is running and whether there are Idle threads in the core Thread Pool, after all the conditions are met, compareAndIncrementWorkerCount will be called to first increase the number of running threads by 1. If the number auto-increment succeeds, the thread will jump out of the loop. If the auto-increment fails, the thread will continue from the beginning */4... 5 if (compareAndIncrementWorkerCount (c) 6 break retry; 7... 8/* after the number of running threads is successfully increased, the thread is encapsulated into a Worker thread */9 boolean workerStarted = false; 10 boolean workerAdded = false; 11 Worker w = nu Ll; 12 try {13 final ReentrantLock mainLock = this. mainLock; // global lock 14 w = new Woker (firstTask); // encapsulate the Thread as Worker Thread 15 final Thread t = w. thread; 16 if (t! = Null) {17 mainLock. lock (); // obtain the global lock 18/* when the global lock is held, check the running status of the thread pool again */19 try {20 int c = clt. get (); 21 int rs = runStateOf (c); // thread pool running status 22 if (rs <SHUTDOWN | (rs = SHUTDOWN & firstTask = null )) {// The thread pool is running, or the thread pool is closed and the task thread is empty 23 if (t. isAlive () // The thread is active, that is, the thread has started execution or has not died. The correct thread should be a 24 throw new IllegalThreadStateException () that has not yet started execution (); 25 workers. add (w); // private final HashSet <Worker> wokers = new Has HSet <Worker> (); contains all Worker threads in the thread pool, which can be accessed only when the global information is obtained. Add the newly constructed worker thread to the worker thread set 26 int s = worker. size (); // Number of worker threads 27 if (s> largestPoolSize) 28 largestPoolSize = s; 29 workerAdded = true; // The newly constructed worker thread is successfully added 30} 31} finally {32 mainLock. unlock (); 33} 34 if (workerAdded) {35 t. start (); // after being constructed as a Worker thread and added to the collection of Worker threads, run the thread task. Note that the start here actually executes the run method in the Worker, so next we will analyze the Worker run method 36 workerStarted = true; 37} 38} 39} finally {40 if (! WorkerStarted) // failed to create execution thread 41 addWorkerFailed (w); // After the worker thread fails to start, remove the worker thread 42} 43 return workerStarted; 44}
In code 35th above, after a Worker thread is successfully added to the Worker thread collection, start execution starts. Here start executes the run method in the Worker thread.
// ThreadPoolExecutor $ Worker, which inherits AQS and implements Runnable, so it has all the features of both private final class Worker extends actqueuedsynchronizer implements Runnable {final Thread thread; runnable firstTask; public Worker (Runnable firstTask) {setState (-1); // set the synchronization status of AQS to-1 and disable interruption until runWorker this is called. firstTask = firstTask; this. thread = getThreadFactory (). newThread (this); // create a thread through the thread factory and pass itself as Runnable} public void run () {runWorker (this); // run the working thread }}
ThreadPoolExecutor # runWorker ), that is to say, a Worker is not only released or ended after the task is executed, but is not idle. Instead, it continues to obtain the task from the task queue until there is no task executable in the task queue, it exits the loop to complete the task. After the above source code is understood, the second and third steps of the thread pool execution principles will be well understood.