Thread pool learning
All the following contents and source code analysis are based on JDK 1.8, please know.
My blog is really out of order, which may be related to my learning style. I think it's not good for me, but I can't convince myself to change, so I can only think about what to learn.
Pooling technology is really a very powerful technology in my opinion, because it achieves the maximum utilization of resources within the limited resources, which reminds me of a course, operational research, which often did this kind of similar problem in operational research at that time.
Let's get down to business. I'll do a thread pool learning next, and I'll also record and share it with you.
Thread pool contains knowledge points related to AQS synchronizer. If you feel a little weak about AQS synchronizer knowledge points, you can see my last article.
Advantages of Thread Pool
Since we are talking about thread pools, and most Daniels will also suggest that we use pooling technology to manage some resources, thread pools certainly have its advantages, otherwise how can they be so famous and used by everyone?
Let's see what advantages it has.
Resource Controllability: Using thread pools avoids memory consumption by creating a large number of threads
Improving response speed: Thread pool creation is actually time-consuming and performance-intensive. Thread pool creation can run with tasks and improve response speed.
Easy to manage: One of the most prominent features of pooling technology is that it can help us manage the resources in the pool. It is allocated and managed by thread pool.
Creation of thread pool
We need to use thread pool to distribute and manage our threads. First, we need to create a thread pool, or there are many bulls who have helped us write a lot of code. Executors'factory method provides us with a variety of ways to create different thread pools. Because this class is only a factory for creating objects and does not involve many specific implementations, I will not go into too much detail.
The old rule is to code directly.
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
Here's an example of a method to illustrate later. We can see that Executors is just a factory, and methods are just to instantiate different objects. In fact, the key class to instantiate is ThreadPoolExecutor. Now let's briefly explain each parameter in the ThreadPoolExecutor constructor.
CorePoolSize (Core Thread Pool Size): When a task is submitted to the thread pool, the thread pool creates a thread to execute the task. Even if other idle basic threads can execute a new task, the thread will be created. When the number of tasks is larger than the number of core threads, it will not be created again. One thing to note here is that when the thread pool was created, it did not create any threads, but waited for tasks to create threads, unless the prestartAllCoreThreads() or prestartCoreThread() method was invoked, so that corePoolSize threads or a thread could be pre-created.
Maximum PoolSize: The maximum number of threads allowed to be created in the thread pool. If the queue is full and the number of threads created is less than the maximum number of threads, the thread pool will create new threads to perform tasks. It is worth noting that this parameter is meaningless if unbounded queues are used.
Keep AliveTime (thread activity retention time): This parameter will only work if the number of threads is larger than corePoolSize by default, and will terminate when the idle time of the thread reaches keep AliveTime until the number of threads is less than corePoolSize. However, if the allowCoreThreadTimeOut method is called, it also works when the number of threads is less than corePoolSize.
Unit (keel AliveTime time unit): There are seven kinds of keel AliveTime units, which are not listed here.
-
WorkQueue (blocking queue): Blocking queue is used to store tasks waiting to be executed. This parameter is also very important. Here are a few blocking queues.
Array BlockingQueue: This is a bounded blocking queue based on array structure, which sorts elements according to FIFO principles.
LinkedBlockingQueue: A linked list-based blocking queue with FIFO-ordered elements and generally higher throughput than Array BlockingQueue. The static factory method Executors.newFixedThreadPool() uses this queue.
SynchronousQueue: A blocking queue that does not store elements. Each insert operation must wait until another thread calls the removal operation, otherwise the insert operation will remain blocked. Throughput is usually higher than LinkedBlockingQueue, which is used by the static factory method Executors.newCachedThreadPool().
Priority Blocking Queue: A non-blocking queue with priority.
-
Handler (saturation strategy); when the thread pool and queue are full, indicating that the thread pool is already saturated, then a strategy must be adopted to deal with the new tasks still submitted. This saturation policy is AbortPolicy by default, indicating that an exception is thrown when a new task cannot be handled. There are four saturation strategies to provide, of course, we can also choose our own saturation strategy.
AbortPolicy: Discard and throw the RejectedExecutionException exception directly
CallerRunsPolicy: Run tasks only with the thread in which the caller lives.
Discard Oldest Policy: Discard the latest task in the queue and execute the current task.
DiscardPolicy: Discards tasks and does not throw exceptions.
The execution process of thread pool is introduced with the figure in the reference. Specifically, we explain it through code.
On the above, we briefly explained the factory method in Executors factory class, and described some parameters of creating thread pools and their functions. Of course, the above explanation is not very in-depth, because it takes time to understand what you want to understand, and the blogger himself still does not fully understand, but the blogger's learning method is. After learning a general outline, it may be better to look back at the previous knowledge points, so let's go on to the following.
ThreadPool Executor Source Code Analysis
We found that the factory method of Executors mainly returns the ThreadPoolExecutor object, but the other one is not mentioned here for the moment, that is to say, to learn thread pool, the key is to learn to analyze the source code in the ThreadPoolExecutor object, and then we will analyze the key code in the ThreadPoolExecutor.
AtomicInteger ctl
ctl is the main control state and a compound variable, which includes two concepts.
workerCount: Represents the number of valid threads
-
runState: The running state of threads in the thread pool
Let's analyze some of the source code related to ctl and go directly to the code.
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //The number of digits used to represent the number of thread pools is obviously 29, Integer.SIZE=32 private static final int COUNT_BITS = Integer.SIZE - 3; //Maximum number of thread pools, 2 ^ 29 - 1 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits //We can see that there are five runState states, proving that at least three bits are needed to represent the runState state. //So the top three is runState. private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS; // Packing and unpacking ctl private static int runStateOf(int c) { return c & ~CAPACITY; } private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; } //Blocking queues for storing thread tasks private final BlockingQueue<Runnable> workQueue; //Reentrant lock private final ReentrantLock mainLock = new ReentrantLock(); //The collection of threads in the thread pool can only be accessed if it has a mainLock lock private final HashSet<Worker> workers = new HashSet<Worker>(); //Waiting conditions support termination private final Condition termination = mainLock.newCondition(); //Create thread factories for new threads private volatile ThreadFactory threadFactory; //Saturation strategy private volatile RejectedExecutionHandler handler;
-
CAPACITY
Let's talk about the calculation of the maximum number of thread pools, because it involves operations such as source code and displacement. I feel that most people still don't know much about this, because I didn't know much when I first saw it.
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
From the code, we can see that it is necessary to move 1 to the left 29 bits, and then subtract 1. How does that move 1 to the left 29 bits calculate?
1 << COUNT_BITS The 32-bit binary of 1 is 00000000 00000000 00000000 00000001 If you move 29 places to the left, that's it. 00100000 00000000 00000000 00000000 Subtract one more operation 000 11111 11111111 11111111 11111111 That is to say, the maximum number of thread pools is 000 11111 11111111 11111111 11111111
2.runState
Positive primitive codes, inverse codes and complements are all the same.
At the bottom of the computer, it is represented by complements.
private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS;
-
RUNNING
New tasks are acceptable and tasks that are already blocked are processed
If all three digits are 1, it's RUNNING status.
-1 << COUNT_BITS Here is - 1 shifting 29 bits to the left, slightly different, - 1, we need to figure out the complement by ourselves. The original code of -1 10000000 00000000 00000000 00000001 - The inverse code of 1, the inverse code of negative number is the inverse of all the original codes except the symbol bits. 11111111 11111111 11111111 11111110 - The complement of 1, the complement of negative number is the countercode + 1 11111111 11111111 11111111 11111111 Crucially, move 29 bits to the left, so all three bits are in the RUNNING state. 111 00000 00000000 00000000 00000000
-
SHUTDOWN
Do not accept new tasks, but handle tasks that are already blocking queues
High 3 bits are all 0, that is SHUTDOWN state
0 << COUNT_BITS 0 representation 00000000 00000000 00000000 00000000 Move 29 bits to the left 00000000 00000000 00000000 00000000
-
STOP
Do not accept new tasks, do not handle tasks in the blocking queue, and interrupt tasks being processed
So the top three is 001, which is the STOP state.
1 << COUNT_BITS 1 representation 00000000 00000000 00000000 00000001 Move 29 bits to the left 00100000 00000000 00000000 00000000
-
TIDYING
All tasks are aborted, workerCount is 0, thread state is converted to TIDYING and terminated() hook method is called
So the top three is 010, which is the TIDYING state.
2 << COUNT_BITS 32-bit binary of 2 00000000 00000000 00000000 00000010 Move 29 bits to the left 01000000 00000000 00000000 00000000
-
TERMINATED
terminated() hook method has been completed
So the top three is 110, which is the TERMINATED state.
3 << COUNT_BITS 32-bit binary of 3 00000000 00000000 00000000 00000011 Move 29 bits to the left 11000000 00000000 00000000 00000000
3. Introduction of some methods
- runStateOf(int c)
Real-time acquisition of runState
private static int runStateOf(int c) { return c & ~CAPACITY; }
~CAPACITY ~ It means to take the opposite position. & It means by position and by position. And CAPACITY is, high 3 0, low 29 are all 1, so it is. 000 11111 11111111 11111111 11111111 If the opposite is true, 111 00000 00000000 00000000 00000000 The incoming c-parameters and the retrieved CAPACITY are bitwise and operated 1. Low 29 zeros for bit-by-bit and, or 29 zeros 2. High position 3 1, keeping the high position 3 of c parameter By keeping the high bit as it is and the low 29 bit as zero, you get the runState running state of the thread pool.
- workerCountOf(int c)
Gets the current number of valid threads in the thread pool
private static int workerCountOf(int c) { return c & CAPACITY; }
The 32-bit binary of CAPACITY is 000 11111 11111111 11111111 11111111 Bit-by-bit and operation with reference c and CAPACITY 1. Low 29 bits are all 1, so keep the low 29 bits of c, that is, the number of valid threads. 2. The high three bits are all 0, so the high three bits of c are also 0. So what you get is the value of workerCount.
-
ctlOf(int rs, int wc)
Initialization Method of Atomic Integer Variable ctl
//Consider these lines of code private static final int RUNNING = -1 << COUNT_BITS; private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static int ctlOf(int rs, int wc) { return rs | wc; }
RUNNING is 111 00000 00000000 00000000 00000000 ctlOf is a bit or operation of rs and wc Initialize RUNNING and 0 bit by bit or The 32-bit binary of 0 is 00000000 00000000 00000000 00000000 So the initialized ctl is 111 00000 00000000 00000000 00000000
Core Method Source Code Analysis
- execute(Runnable command) method
public void execute(Runnable command) { //The task command to be executed is empty, throwing a null pointer exception if (command == null) // 1 throw new NullPointerException(); /* *The process of execution is actually divided into three steps *1,If the running thread is smaller than corePoolSize, a new thread is opened to execute with the Runable object given by the user. * And executing the addWorker method checks runState and workerCount with atomic operations to prevent when returning false * When adding threads that should not be added *2, If the task can be successfully added to the queue, we still need to double-check the added threads, possibly before adding the threads. * A check is dead, or the thread pool is closed when it enters the method. So we need to review the status, and there is a need. * If you want to, you need to roll back the column operation when you stop, or open a new thread when there are no threads. *3,If the task cannot be listed, we need to try adding a new thread. If the new thread fails, we know that the thread may be closed. * Or if it's saturated, it needs to be rejected. * */ //Get the control status of thread pool int c = ctl.get(); // 2 //WorkCount value calculated by workCountOf method is less than corePoolSize if (workerCountOf(c) < corePoolSize) { //Add tasks to worker collections if (addWorker(command, true)) return; //Successful return //Get the control status of the thread pool again if it fails c = ctl.get(); } /* *Determine whether the thread pool is in RUNNING state *Yes, add Runnable objects to the workQueue queue */ if (isRunning(c) && workQueue.offer(command)) { // 3 //Get the state of the thread pool again int recheck = ctl.get(); //Check status again //Thread pool is not in RUNNING state, removing tasks from workQueue queue if (! isRunning(recheck) && remove(command)) //Refusal task reject(command); //workerCount equals 0 else if (workerCountOf(recheck) == 0) // 4 //Add worker addWorker(null, false); } //If joining the blocking queue fails, try to execute the task with the new threads with the maximum number of threads in the thread pool else if (!addWorker(command, false)) // 5 //Failure to execute will result in rejection of tasks reject(command); }
Let's talk about the flow of the code above:
1. First, determine whether the task is empty or not, and then throw an empty pointer exception.
2. Get the thread pool control status if it is not empty, determine that it is less than corePoolSize, and add it to the worker collection for execution.
- If successful, return
- If it fails, it then retrieves the thread pool control state, because only if the state changes will it fail, so retrieve it.
3. Determine whether the thread pool is running or not. If yes, add command to the blocking queue. When joining the queue, the state will be retrieved and detected again.
Whether the state is not running, if not, removes the command from the blocking queue and rejects the task
4. If there are no threads in the thread pool, create a new thread to execute the task of obtaining the blocking queue
5. If none of the above is successful, the thread in the largest thread pool needs to be opened to perform the task, and if it fails, it will be discarded.
Sometimes no amount of text is as clear as a flowchart, so we draw an execute flowchart for you to understand.
2.addWorker(Runnable firstTask, boolean core)
private boolean addWorker(Runnable firstTask, boolean core) { //External loop marker retry: //Outer Dead Cycle for (;;) { //Get thread pool control status int c = ctl.get(); //Get runState int rs = runStateOf(c); // Check if queue empty only if necessary. /** *1.If the thread pool runState is at least SHUTDOWN *2\. One is false and addWorker fails. Look at false. * - runState==SHUTDOWN,That is, the state is greater than SHUTDOWN * - firstTask For null, that is, the incoming task is empty, combined with the above runState is SHUTDOWN, but * firstTask Not empty. Represents that the thread pool has been closed and the task is still passing in. * - Queue empty, since the task is empty, queue empty, there is no need to add tasks to the thread pool */ if (rs >= SHUTDOWN && //runState greater than or equal to SHUTDOWN, initial bit RUNNING ! (rs == SHUTDOWN && //runState equals SHUTDOWN firstTask == null && //First Task is null ! workQueue.isEmpty())) //The workQueue queue is not empty return false; //Inner Dead Cycle for (;;) { //Get the number of workerCount s in the thread pool int wc = workerCountOf(c); //If workerCount exceeds or is larger than corePoolSize/maximumPoolSize //Return to false if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //Through CAS operation, the number of workerCount + 1, success will jump out of the loop, back to the retry tag if (compareAndIncrementWorkerCount(c)) break retry; //CAS operation failed to retrieve the control status of thread pool again c = ctl.get(); // Re-read ctl //If the current runState is not equal to the newly acquired runState, jump out of the inner loop and continue the outer loop if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop //CAS failed to change workerCount and continued the inner loop } } //Through the above loop, you can execute until this is workerCount success + 1 //worker start tag boolean workerStarted = false; //worker add tag boolean workerAdded = false; //Initialize the worker to null Worker w = null; try { //Initialize a worker object of the current Runnable object w = new Worker(firstTask); //Get the thread corresponding to the worker final Thread t = w.thread; //If the thread is not null if (t != null) { //Locks in the Initial Thread Pool final ReentrantLock mainLock = this.mainLock; //Get lock mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. //Check again after getting the lock to get the thread pool runState int rs = runStateOf(ctl.get()); //When runState is less than SHUTDOWN or runState is equal to SHUTDOWN and first Task is null if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { //Threads are alive if (t.isAlive()) // precheck that t is startable //Threads survive without starting, throwing IllegalThreadStateException exceptions throw new IllegalThreadStateException(); //Add the worker object to the workers collection workers.add(w); //Get the size of the workers collection int s = workers.size(); //If the size exceeds largestPoolSize if (s > largestPoolSize) //Reset largestPoolSize largestPoolSize = s; //Markup worker has been added workerAdded = true; } } finally { //Release lock mainLock.unlock(); } //If worker adds successfully if (workerAdded) { //Startup thread t.start(); //Markup worker has been started workerStarted = true; } } } finally { //If the worker does not start successfully if (! workerStarted) //Operation of workerCount-1 addWorkerFailed(w); } //Returns the label whether the worker is started or not return workerStarted; }
Let's also briefly talk about the process of this code. It's really very difficult. The blogger stopped writing many times and wanted to break the keyboard.
1. Obtain the control status of thread pool and make judgment. If it does not conform, it will return false. If it does, it will be the next step.
2. Dead loop, judge whether workerCount is larger than the upper limit or larger than corePoolSize/maximumPoolSize, and if not, operate on workerCount+1.
3. If the above judgment or + 1 operation fails, get the control state of the thread pool again. If the runState is inconsistent with the newly acquired runState, jump out of the inner loop and continue the outer loop, otherwise continue the inner loop.
4. +1 operation is successful. ReentrantLock is used to ensure that a worker instance is added to the workers. If the worker instance is added successfully, the instance is started.
Next, take a look at the flow chart to understand an execution process of the above code.
3.addWorkerFailed(Worker w)
When the addWorker method fails to add a worker and fails to start a task successfully, it calls this method, removes the task from the workers, and the workerCount does the - 1 operation.
private void addWorkerFailed(Worker w) { //Reentrant lock final ReentrantLock mainLock = this.mainLock; //Get lock mainLock.lock(); try { //If the worker is not null if (w != null) //workers remove worker workers.remove(w); //Through CAS operation, workerCount-1 decrementWorkerCount(); tryTerminate(); } finally { //Release lock mainLock.unlock(); } }
4.tryTerminate()
When an abnormal successful logic operation is performed on the thread pool, tryTerminate attempts to terminate the thread pool are required.
final void tryTerminate() { //Dead cycle for (;;) { //Get thread pool control status int c = ctl.get(); /* *Thread pool is in RUNNING state *Thread pool state minimum greater than TIDYING *Thread pool == SHUTDOWN and workQUeue is not empty *Direct return, not termination */ if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) return; //If workerCount is not zero if (workerCountOf(c) != 0) { // Eligible to terminate interruptIdleWorkers(ONLY_ONE); return; } //Get the lock for the thread pool final ReentrantLock mainLock = this.mainLock; //Get lock mainLock.lock(); try { //Set the thread pool state to TIDYING by CAS operation if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { try { terminated(); } finally { //Set the status of thread pool to TERMINATED ctl.set(ctlOf(TERMINATED, 0)); //Send a release signal to a thread waiting on termination condition termination.signalAll(); } return; } } finally { //Release lock mainLock.unlock(); } // else retry on failed CAS } }
5.runWorker(Worker w)
The purpose of this method is to perform tasks.
final void runWorker(Worker w) { //Get the current thread Thread wt = Thread.currentThread(); //Get the tasks in the worker Runnable task = w.firstTask; //Assign the task of the worker instance to null w.firstTask = null; /* *unlock Method calls the release method of AQS *release Method calls the tryRelease method of the concrete implementation class, Worker *That is to say, the AQS state is set to 0, allowing interruption. */ w.unlock(); // allow interrupts //Suddenly completed or not boolean completedAbruptly = true; try { //The task of the worker instance is not empty, or the task obtained through getTask is not empty. while (task != null || (task = getTask()) != null) { //Get lock w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt /* *Get the control state of the thread pool, at least greater than the STOP state *If the status is incorrect, check whether the current thread is interrupted and clear the interrupt status, and check again whether the thread pool status is greater than STOP. *If the above is satisfied, check whether the object is in an interrupt state and do not clear the interrupt flag. */ if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) //Interrupt change object wt.interrupt(); try { //Pre-execution methods are implemented by subclasses beforeExecute(wt, task); Throwable thrown = null; try { //Perform tasks task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { //The method called after execution is also implemented by subclasses. afterExecute(task, thrown); } } finally {//After execution //task is set to null task = null; //Number of completed tasks + 1 w.completedTasks++; //Release lock w.unlock(); } } completedAbruptly = false; } finally { //Processing and exiting the current worker processWorkerExit(w, completedAbruptly); } }
Next, we will use words to illustrate the specific logic and process of the task execution method.
- First, as soon as the method comes in, w.unlock() is executed in order to change the state of AQS to 0, because only when getState () >= 0 can the thread be interrupted.
- To determine whether first Task is empty or not, the task is acquired by getTask() instead of empty and then executed downward.
- Determine whether the interruption status is met, and set the interruption marker if it meets the interruption status.
- Execute beforeExecute(), task.run(), afterExecute() methods
- Any exception will result in the termination of task execution; enter processWorkerExit to exit the task
- If it works properly, it goes back to step 2.
Attached is a simple flow chart:
6.getTask()
In the runWorker method above, we can see that when the first Task is empty, it will be used to get tasks to execute. So let's see what the method of getting tasks is like.
private Runnable getTask() { //Task timeout logo acquisition boolean timedOut = false; // Did the last poll() time out? //Dead cycle for (;;) { //Get the control status of thread pool int c = ctl.get(); //Get the runState of the thread pool int rs = runStateOf(c); // Check if queue empty only if necessary. /* *To determine the state of the thread pool, there are two situations *1,runState State greater than or equal to SHUTDOWN *2,runState Over or equal to STOP or blocking queue empty *workerCount-1 will be operated through CAS and null will be returned */ if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } //Get workerCount for thread pool int wc = workerCountOf(c); // Are workers subject to culling? /* *allowCoreThreadTimeOut: Whether core Thread timeout is allowed, default false *workerCount Is it larger than the core core thread pool? */ boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; /* *1,wc Greater than maximumPoolSize or timed out *2,Queues are not space-time guaranteed to have at least one task */ if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { /* *Through CAS operation, workerCount-1 *Can perform - 1 operation to prove that wc is greater than maximumPoolSize or has timed out */ if (compareAndDecrementWorkerCount(c)) //- 1 Operate successfully, return null return null; //- 1 Operation failed, continue cycle continue; } try { /* *wc Larger than the core thread pool *Execute poll method *Less than the core thread pool *Executing the take method */ Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); //Judging that the task is not an empty return task if (r != null) return r; //Acquisition is not available for a period of time. Acquisition timeout timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
Or explain the above code logic and process in words:
- Get the thread pool control status and runState to determine whether the thread pool has been closed or is closing, and if so, the workerCount-1 operation returns null
- Get workerCount to determine whether it is larger than the core thread pool
- Determine whether workerCount is larger than the maximum number of thread pools or has timed out. Yes, workerCount-1 returns null if it succeeds, and if it fails, it goes back to step 1 and resumes.
- Determine whether workerCount is larger than the core thread pool or not, then use poll method to get tasks from the queue, otherwise use take method to get tasks from the queue.
- Determine whether the task is empty, return to the acquired task if it is not empty, or go back to step 1 and resume.
Next there is a flow chart:
7.processWorkerExit
Obviously, in the execution of tasks, we will get the task to execute. Since it is the execution of tasks, there will definitely be execution or abnormal interruption of execution. At that time, there will certainly be corresponding operations. As for the specific operation, we still directly see the source code is the most practical.
private void processWorkerExit(Worker w, boolean completedAbruptly) { /* *completedAbruptly:In runWorker, the meaning of whether or not it was suddenly completed *That is, when an exception occurs during the execution of a task, it will suddenly complete and pass true. * *If it is completed suddenly, it needs to be operated through CAS, workerCount-1 *If it's not done suddenly, it doesn't need - 1, because - 1 is already in the getTask method. * *The following code comment seems to have the opposite meaning of the code */ if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); //Generate reentrant locks final ReentrantLock mainLock = this.mainLock; //Get lock mainLock.lock(); try { //Completed TaskCount plus the number of tasks completed in the worker completedTaskCount += w.completedTasks; //Remove from HashSet < Worker > workers.remove(w); } finally { //Release lock mainLock.unlock(); } //Because the above operation is to release tasks or threads, the state of the thread pool is determined and the thread pool is attempted to terminate. tryTerminate(); //Get the control status of thread pool int c = ctl.get(); //Judging whether runState is STOP, that is, RUNNING or SHUTDOWN //If it is RUNNING or SHUTDOWN, it means that the thread pool has not been successfully terminated. if (runStateLessThan(c, STOP)) { /* *Suddenly completed or not *If not, the delegate has no task to complete because the while loop is in getTask */ if (!completedAbruptly) { /* *allowCoreThreadTimeOut:Whether core thread timeout is allowed, default false *min-The default is corePoolSize */ int min = allowCoreThreadTimeOut ? 0 : corePoolSize; //Allow core thread s to timeout and the queue is not empty //min is 0, which allows core threads to timeout, so there is no need to maintain the core thread pool //If workQueue is not empty, keep at least one thread alive if (min == 0 && ! workQueue.isEmpty()) min = 1; //If the workerCount is greater than min, it means that it meets the requirements and can be returned directly. if (workerCountOf(c) >= min) return; // replacement not needed } //If it's done suddenly, add a worker thread for an empty task -- I don't quite understand that here. addWorker(null, false); } }
- First, determine whether the thread terminates abruptly, if it terminates abruptly, through CAS, workerCount-1
- Statistics the number of tasks completed by the thread pool and remove the worker from the workers
- Determine the state of the thread pool and try to terminate the thread pool
- Thread pool did not terminate successfully
- Judging whether a task is suddenly completed is not the next step, but the third step.
- If the core thread is allowed to timeout and the queue is not empty, at least one thread is guaranteed to survive.
- Add a worker thread for an empty task
- Judging whether a task is suddenly completed is not the next step, but the third step.
Worker inner class
We have already talked about the execution process and some details of thread pool execution task execute in detail. There is a word frequently appearing on the thread pool execution task execute, that is, worker instance, so what is the worker actually? What information does it contain, and how does the worker actually perform this task?
Let's introduce it in this section, or go directly to the source code:
We can see that the inner class of Worker inherits the AQS synchronizer and implements the Runnable interface, so Worker is obviously a class that can execute tasks and control interruption and lock effect.
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * This class will never be serialized, but we provide a * serialVersionUID to suppress a javac warning. */ private static final long serialVersionUID = 6138294804551838833L; /** Work threads are null if the factory fails. */ final Thread thread; /** Initialize tasks, possibly empty */ Runnable firstTask; /** Number of completed tasks */ volatile long completedTasks; /** * Create and initialize the first task, using thread factories to create threads * Initialization has three steps *1,Set the synchronization state of AQS to - 1 to indicate that the object needs to be waked up *2,Initialize the first task *3,Call ThreadFactory to create a thread itself and assign it to the worker's member variable thread */ Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } //Rewriting Runnable's run Method /** Delegates main run loop to outer runWorker */ public void run() { //Calling the runWorker method of ThreadPoolExecutor runWorker(this); } // Lock methods // // The value 0 represents the unlocked state. // The value 1 represents the locked state. //Represents whether exclusive locks, 0-non-exclusive 1-exclusive locks protected boolean isHeldExclusively() { return getState() != 0; } //Rewriting the tryAcquire method of AQS to try to acquire locks protected boolean tryAcquire(int unused) { //Attempt to change the synchronization state of AQS from 0 to 1 if (compareAndSetState(0, 1)) { //If changed to, set the current exclusive mode thread to the current thread and return true setExclusiveOwnerThread(Thread.currentThread()); return true; } //Otherwise return false return false; } //tryRelease, which rewrites AQS, tries to release the lock protected boolean tryRelease(int unused) { //Set the current exclusive mode thread to null setExclusiveOwnerThread(null); //Set AQS synchronization status to 0 setState(0); //Return to true return true; } //Get lock public void lock() { acquire(1); } //Attempt to acquire locks public boolean tryLock() { return tryAcquire(1); } //Release lock public void unlock() { release(1); } //Is it monopolized? public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } } }
Summary
Writing this thread pool is really not easy. It took two weeks. There are many places in the middle that I don't understand. Moreover, the introduction of thread pool in the book "The Art of Java Concurrent Programming" is not very much. So I look very painful. I often read this method, and I don't know why to call this method and what the purpose of calling this method is. And in the process of learning, there are doubts about their learning methods, because some people told me that they do not need a sentence to analyze the source code, just need to know the process, but later still want to follow their own learning route, read more source code is always good, here I also give some suggestions to apes, when they have their own learning methods. We should stick to it in our own way.
Reference material
Fang Tengfei: The Art of Java Concurrent Programming
If you need to reproduce, please be sure to indicate the source, after all, it is not easy to move bricks one by one.