ThreadPoolExecutor task submission and stop process and underlying implementation

Posted by jmaker on Wed, 09 Mar 2022 13:50:35 +0100

ThreadPoolExecutor task submission

executor task submission process

It can be seen from the source code that the Excutor interface under JUC only provides an executable method executor

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

The description in JDK notes is "execute the given command at some time in the future.", Execute the given command at a certain time in the future, that is, submit the task to the thread pool. For execution at a certain time in the future, the submitted task must implement the Runnable interface, and the void type makes the submission method unable to obtain the return value.

The JDK source code of the executor is as follows:

Perform a given task at some point in the future. This task is executed with a new thread or with an existing thread in the thread pool. This task is executed with a new thread or with an existing thread in the thread pool.

If the task cannot be submitted for execution, either because the Executor has been shut down or its capacity limit has been reached, the task will be processed by the current RejectedExecutionHandler.

  public void execute(Runnable command) {
         if(command == null) throw new NullPointerException;
         int c = ctl.get();

        /**
         * 1,If the current number of threads is less than corePoolSize
         *(It may be that the addWorker() operation has included the judgment of the thread pool status, which is not added here, but added before entering the workQueue)
         */
        if (workerCountOf(c) < corePoolSize) {
            //addWorker() succeeded, return
            if (addWorker(command, true))
                return;

            /**
             * addWorker() failed to get c again (ctl.get() will be called again when ctl is needed to judge again)
             * The reasons for the failure may be:
             * 1,The thread pool has been shut down, and the shut down thread pool will no longer receive new tasks
             * 2,workerCountOf(c) < corePoolSize After judgment, due to concurrency,
             * Other threads created worker threads first, resulting in workercount > = corepoolsize
             */
            c = ctl.get();
        }

        /**
         * 2,If the thread pool is in RUNNING status and the queue entry is successful
         */
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();//Recheck bit

            /**
             * Verify again whether the tasks put into the workerQueue can be executed
             * 1,If the thread pool is not running, you should refuse to add new tasks and delete tasks from the workQueue
             * 2,If the thread pool is running, or deleting a task from the workQueue fails
             *(Just one thread has finished executing and consumed the task), and ensure that there are still threads executing the task (as long as there is one is enough)
             */
            //If the thread pool is not in the RUNNING state during the re verification process,
            //And remove (command) -- workqueue Remove() succeeded, rejecting the current command
            if (! isRunning(recheck) && remove(command))
                reject(command);
                //If the current number of workers is 0, create a thread through addWorker(null, false) and its task is null
                //Why only check whether the number of worker s running is 0?? Why not compare with corePoolSize??
                //Only one worker thread can get the task execution from the queue??
                //Because as long as there are active worker threads, you can consume tasks in workerQueue
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
            //The first parameter is null, which means that only a worker thread is created and firstTask is not specified
            //The second parameter is true, which means corePoolSize is occupied, and false, which means maxPoolSize is occupied
        }
        /**
         * 3,If the thread pool is not running or cannot be queued
         * Try to start a new thread and expand the capacity to maxPoolSize. If addWork(command, false) fails,
         * Reject current command
         */
        else if (!addWorker(command, false))
            reject(command);
    }    
}
  1. If the command pointer is null, you need to get the null error first
  2. If the number of running threads is less than corePoolSize, create a new thread through addWorker to execute the newly added task to run command. Command will be the first task of this thread, and the call to addWorker will atomically check runState and workerCount.
  3. If the task is successfully put into the queue, a double check mechanism is still needed to confirm whether a new thread should be created. Because the existing thread died after the last check or the thread pool was closed after entering this method, the state needs to be checked again. It is necessary to roll back the queue. If there are no threads in the thread pool, start a thread.
  4. If the task cannot be queued (the queue may be full), try to add a new thread. If it fails, the thread pool is closed or saturated, so the task execution is rejected.

Thread pool execution process

  1. If the number of threads in the thread pool is less than corePoolSize, a new thread is created to perform the newly added task
  2. If the number of threads in the thread pool is greater than or equal to corePoolSize, but the queue workQueue is not full, put the newly added task into the workQueue
  3. If the number of threads in the thread pool is greater than or equal to corePoolSize, and the queue workQueue is full, but the number of threads in the thread pool is less than maximumPoolSize, a new thread will be created to process the added task
  4. If the number of threads in the execution pool is equal to the number of threads in the execution pool, the execution pool will be rejected

Thread pool status

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
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; }

Among them, ctl, an AtomicInteger, has powerful functions, including Integer4byte and 32bit. The upper 3 bits are used to maintain the running state of the thread pool, and the lower 29 bits are used to maintain the number of threads in the thread pool

1,RUNNING: -1<<COUNT_ Bits, that is, the upper 3 bits are 1 and the lower 29 bits are 0. The thread pool in this state will receive new tasks and process tasks waiting in the blocking queue

2,SHUTDOWN: 0<<COUNT_ Bits, that is, the upper 3 bits are 0 and the lower 29 bits are 0. The thread pool in this state will not receive new tasks, but will also process tasks that have been submitted to the blocking queue for processing

3,STOP: 1<<COUNT_ Bits, that is, the upper 3 bits are 001 and the lower 29 bits are 0. The thread pool in this state will not receive new tasks, will not process tasks waiting in the blocking queue, and will interrupt running tasks

4,TIDYING: 2<<COUNT_ Bits, that is, the upper 3 bits are 010 and the lower 29 bits are 0. All tasks are terminated and workerCount is 0. In this state, the terminated() method will also be called

5,TERMINATED: 3<<COUNT_ Bits, that is, the upper 3 bits are 100 and the lower 29 bits are 0. It becomes this state after the call of the terminated() method is completed

These states are represented by int type, and the size relationship is running < shutdown < stop < tidying < terminated. This order basically follows the process of thread pool from running to termination.

runStateOf(int c) method: C & the ~ capability with the upper 3 bits as 1 and the lower 29 bits as 0, which is used to obtain the thread pool state saved by the upper 3 bits

workerCountOf(int c) method: C & capability with the upper 3 bits as 0 and the lower 29 bits as 1, which is used to obtain the number of threads with the lower 29 bits

ctlOf(int rs, int wc) method: parameter rs represents runState and parameter wc represents workerCount, that is, it is packaged and combined into ctl (atomic operation class) according to runState and workerCount

addWorker add worker thread

/**
 * Check whether a new worker can be created according to the status of the current thread pool and the given core or maximum
 * If so, adjust the number of workers accordingly. If possible, create a new worker and start it,
 * The firstTask in the parameter is the first task of the worker
 * If the method returns false, it may be because the pool has been closed or shutdown has been called
 * If the thread factory fails to create a thread, it will also fail and return false
 * If the thread creation fails, either the thread factory returns null or OutOfMemoryError occurs
 */
private boolean addWorker(Runnable firstTask, boolean core) {
    //The outer loop is responsible for judging the thread pool state
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c); //state
        /**
         * The smaller the state of the thread pool, the more it is running. runnbale=-1, shutdown=0,stop=1,tidying=2, terminated=3
         * 1,If the thread pool state is at least in shutdown state
         * 2,And any of the following three conditions is false
         *   rs == SHUTDOWN         
         *   (Implied: RS > = shutdown) false: the thread pool status has exceeded shutdown,
         *   It may be one of stop, tidying and terminated, that is, the thread pool has been terminated
         *   
         *   firstTask == null      
         *  (Implied: rs==SHUTDOWN) false condition:
         *    firstTask Not null, rs==SHUTDOWN and firstTask is not null, return false,
         *    The scenario is to add a new task and reject it after the thread pool has been shut down
         *
         *   ! workQueue.isEmpty()  
         * (Implied: rs==SHUTDOWN, firstTask==null) false case: workQueue is empty,
         *  When firstTask is empty, it is to create a thread without tasks, and then obtain tasks from workQueue,
         *  If the workQueue is empty, there is no need to add a new worker thread
         *
         * return false,That is, addWorker() cannot be
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        
        
        if(rs != SHUTDOWN) //If the status is greater than or equal to shutdown, false is returned
        if(firstTask != null)  It's a newly submitted task shutdown Status and after that, it will not be executed and will return false
        if(workQueue.isEmpty()) If the queue is empty, there is no need to add new threads worker   
 
        //Inner circulation, responsible for the number of worker s + 1
        for (;;) {
            int wc = workerCountOf(c); //Number of worker s
             
            //If the number of worker s > the maximum CAPACITY of the thread pool (i.e. the maximum value that can be accommodated by using the lower 29 bits of int)
            //Or (number of workers > corepoolsize or # number of workers > maximumpoolsize), that is, the given boundary has been exceeded
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
             
            //Call the unsafe CAS operation to make the number of worker s + 1. If successful, the retry loop will jump out
            if (compareAndIncrementWorkerCount(c))
                break retry;
             
            //CAS worker count + 1 failed, read ctl again
            c = ctl.get();  // Re-read ctl
             
            //If the state is not equal to the previously obtained state, jump out of the inner loop and continue to judge the outer loop
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
            // When else CAS fails, because the workerCount changes, continue the inner loop and try to add 1 to the number of workers by CAS
        }
    }
 
    /**
     * worker Quantity + 1 successful subsequent operation
     * Add to the workers Set collection and start the worker thread
     */
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        final ReentrantLock mainLock = this.mainLock; 
        w = new Worker(firstTask); //1. Set the synchronization status of the worker AQS lock state=-1
                                   //2. Set firstTask to the member variable firstTask of the worker
                                   //3. Call the thread of the factory to create a variable for the thread
        final Thread t = w.thread;
        if (t != null) {
            mainLock.lock();
            try {
                //--------------------------------------------This part of the code is locked
                // When the lock is obtained, check again
                int c = ctl.get();
                int rs = runStateOf(c);
 
                //If the thread pool is running < shutdown or the thread pool has been shutdown,
                //And firstTask==null (it may be that there are still unfinished tasks in the workQueue, and a worker thread without an initial task is created for execution)
                //The operation with the number of workers - 1 is in addWorkerFailed()
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // Precheck that is startable: the thread has been started, throwing illegal thread status exceptions
                        throw new IllegalThreadStateException();
                     
                    workers.add(w);//workers is a HashSet < worker >
                     
                    //Set the maximum pool size largestPoolSize and workerAdded to true
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
              //--------------------------------------------
            } 
            finally {
                mainLock.unlock();
            }
             
            //If adding worker to HashSet succeeds, start the thread
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        //If starting the thread fails
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorker(Runnable firstTask, boolean core)

Parameters:

firstTask: the initial task of the worker thread. It can be empty

core: true: take corePoolSize as the upper limit, false: take maximumPoolSize as the upper limit

The addWorker method has four ways to pass parameters:

1,addWorker(command, true)

2,addWorker(command, false)

3,addWorker(null, false)

4,addWorker(null, true)

The first three methods are used in the execute method. Combined with this core method, the following analysis is carried out

The first method: when the number of threads is less than the corePoolSize, put a task to be processed into the Workers Set. If the length of the Workers Set exceeds the corePoolSize, false will be returned

Second: when the queue is full, try to put the new task directly into the Workers Set. At this time, the length limit of the Workers Set is maximumPoolSize. If the thread pool is also full, false is returned

The third type: put an empty task into the workers Set, and the length limit is maximumPoolSize. Such a worker whose task is empty will go to the task queue to get the task when the thread is executing, which is equivalent to creating a new thread, but does not immediately allocate the task

The fourth method is to put a null task into the Workers Set. When it is less than corePoolSize, if the number in the Set has reached corePoolSize, it will return false and do nothing. In actual use, the prestartAllCoreThreads \ (\) method is used to pre start corePoolSize workers for the thread pool and wait for task execution from the workQueue

Execution process:

1. Judge whether the thread pool is currently in the state where worker threads can be added. If yes, continue to the next step. return false is not allowed:

A. Thread pool status > shutdown. It may be stop, tidying or terminated. worker threads cannot be added

B. Thread pool status = = shutdown, firstTask is not empty, and worker thread cannot be added because the thread pool in shutdown status does not receive new tasks

C. Thread pool status = = shutdown, firstTask==null, workQueue is empty, and worker threads cannot be added, because firstTask is empty to add a thread without tasks and then obtain tasks from workQueue, and workQueue is empty, indicating that adding a taskless thread is meaningless

2. Whether the current number of threads in the thread pool exceeds the upper limit (corePoolSize or maximumPoolSize). If it exceeds return false, if not, continue with workerCount+1

3. Under the ReentrantLock guarantee of the thread pool, add a newly created worker instance to the Workers Set, unlock it after adding, and start the worker thread. If all this succeeds, return true. If adding workers to the Set fails or startup fails, call addWorkerFailed() logic

 

Internal class Worker

/**
 * Worker Class generally manages the interrupt status and some indicators of running threads
 * Worker Class opportunistically inherits AbstractQueuedSynchronizer to simplify obtaining and releasing locks when executing tasks
 * This prevents interrupting a running task and only wakes up (interrupts) the thread waiting to get the task from the workQueue
 * Explanation:
 *   Why not directly execute the command submitted by execute(command) and wrap a layer of Worker on the outside??
 *   Mainly to control interruption
 *   With what control??
 *   With AQS lock, when it is locked during operation, it cannot be interrupted. The shutdown() method of TreadPoolExecutor must obtain the worker lock before interruption
 *   You can only break while waiting to get the task getTask() from workQueue
 *
 * worker A simple non reentrant mutex lock is implemented instead of ReentrantLock
 * Because we don't want to get the lock (reentry) again when calling thread pool control methods such as setCorePoolSize()
 * Explanation:
 *   setCorePoolSize()May interrupt idleworkers(), and w.tryLock() when interrupting a thread
 *   If it is reentrant, the thread may be interrupted in the method of operating on the thread pool. Similar methods include:
 *   setMaximumPoolSize()
 *   setKeppAliveTime()
 *   allowCoreThreadTimeOut()
 *   shutdown()
 * In addition, in order to allow the thread to be interrupted after it really starts, the initialization lock status is negative (- 1), set the state to 0 when runWorker() starts, and state > = 0 can be interrupted
 * 
 * Worker It inherits AQS and implements Runnable, which shows that it is both a Runnable task and a lock (non reentrant)
 */
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    final Thread thread; //Thread objects created using ThreadFactory and Worker Runnable
     
    Runnable firstTask;
     
    volatile long completedTasks;
 
    Worker(Runnable firstTask) {
        //Set the synchronization state of AQS private volatile int state, which is a counter. If it is greater than 0, the lock has been obtained
        setState(-1);
        // Interrupt interrupt is prohibited before calling runWorker(),
        //In the interruptIfStarted() method, it will be judged that getstate() > = 0
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); 
        //Create a thread object based on the current worker
       //The current worker itself is a runnable task, that is, it will not create a thread with the firstTask of the parameter,
       //Instead, call the current worker Call firsttask. When run() run()
    }
 
    public void run() {
        runWorker(this); 
        //runWorker() is the method of ThreadPoolExecutor
    }
 
    // Lock methods
    // The value 0 represents the unlocked state. 0 stands for "not locked" status
    // The value 1 represents the locked state. 1 stands for "locked" status
 
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }
 
    /**
     * Attempt to acquire lock
     * Override tryAcquire() of AQS, which is originally implemented by subclasses
     */
    protected boolean tryAcquire(int unused) {
        //Try to set the state from 0 to 1 once, that is, the "locked" state,
        //However, since it is state 0 - > 1 each time, instead of + 1, it indicates that reentry is not allowed
        //And the lock will not be obtained when state==-1
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread()); 
            //Set exclusiveOwnerThread = current thread
            return true;
        }
        return false;
    }
 
    /**
     * Attempt to release lock
     * Not state-1, but set to 0
     */
    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null); 
        setState(0);
        return true;
    }
 
    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }
 
    /**
     * Interrupt (if running)
     * shutdownNow The loop executes on the worker thread
     * And there is no need to obtain the worker lock, which can be interrupted even when the worker is running
     */
    void interruptIfStarted() {
        Thread t;
        //T > state = 0= Null and t not interrupted
        //state==-1 when new Worker(), indicating that it cannot be interrupted
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

The Worker class itself not only implements Runnable, but also inherits AbstractQueuedSynchronizer, so it is both an executable task and a lock

new Worker()

1. Set the state of AQS to - 1, and interrupt is not allowed before runWoker()

2. The task to be executed will be passed in as a parameter and given the firstTask

3. Create Thread with Worker Runnable

The reason why workers implement Runnable and create threads on their own is that they need to control interrupts through workers, and the first task is only responsible for executing business

Worker control interruption mainly includes the following aspects:

1) . the initial AQS status is - 1. interrupt() is not allowed at this time. Interrupt can be performed only after the worker thread is started, runWoker() is executed, and the state is set to 0

Interruptions are not allowed:

  1. When shutting down() thread pool, each worker tryLock() will be locked. The tryAcquire() method of AQS of Worker class is to fix the state from 0 - > 1. Therefore, tryLock() fails when the initial state is state=-1, and there is no way to interrupt interrupt()
  2. When shutting down now() thread pool, you do not need to lock tryLock(), but call worker interruptIfStarted() terminates the worker.interruptIfStarted() also has the logic that state > = 0 can interrupt, and the initial state=-1

The source code of interruptIfStarted() is as follows. You need getstate() > = 0 to interrupt

//Interrupt the worker thread that has executed runworker
void interruptIfStarted() {
    Thread t;
    //w.unlock:state=0;w.lock:state=1. It can be interrupted when runworker and w.unlock are executed
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

2) In order to prevent the running worker from being interrupted under certain circumstances, runWorker() will lock() every time it runs a task, while shutdown() and other operations that may terminate the worker need to obtain the worker's lock first, so as to prevent interrupting the running thread. The AQS implemented by the worker is a non reentrant lock, in order to enter other methods that need to be locked when the worker lock is obtained

runWorker(): execute task

  /**
     * Get and execute tasks from the queue repeatedly, and deal with some problems at the same time:
     * <p>
     * 1,We may start with an initialization task, that is, the firstTask is null
     * Then, as long as the thread pool is running, we get the task from getTask()
     * If getTask() returns null, the worker exits due to changing the thread pool state or parameter configuration
     * Other exits because the external code threw an exception, which will make completedAbruptly true, which will cause the current thread to be replaced in the processWorkerExit() method
     * <p>
     * 2 Before any task is executed, it is necessary to lock the worker to prevent other thread pools from interrupting operations when the task is running
     * clearInterruptsForTaskRun Ensure that unless the thread pool is stopping, the thread will not be set with an interrupt flag
     * <p>
     * 3.beforeExecute() will be called before each task is executed, and an exception may be thrown. In this case, the thread die (jumping out of the loop and completedAbruptly==true) will not execute the task
     * Because the exception of beforeExecute() is not cache d, it will be thrown up and jump out of the loop
     * <p>
     * 4. Assuming beforeExecute() completes normally, we execute the task
     * Summarize any thrown exceptions and send them to after execute (task, throw)
     * Because we can't be in runnable Throw up Throwables again in the run () method. We wrap Throwables into Errors and throw them up (which will be handled by the UncaughtExceptionHandler of the thread)
     * Any thrown exception will cause the thread die
     * <p>
     * 5. After the execution of the task, calling afterExecute() can also throw an exception, which can also lead to thread die.
     * According to JLS Sec 14.20, this exception (the exception in finally) will take effect
     */
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        // new Worker() is state==-1. Here is the tryRelease() method of the Worker class,
        // Set state to 0, and only state > = 0 is allowed to call interrupt in interruptIfStarted()
        boolean completedAbruptly = true; 
        //Whether to "complete suddenly". If the entry is finally caused by an exception, then completedAbruptly==true is a sudden completion
        try {
        /**
         * If the task is not null, or getTask() from the blocking queue is not null
         */
            while (task != null || (task = getTask()) != null) {
                w.lock(); 
                //Locking is not to prevent concurrent execution of tasks, but not to terminate the running worker during shutdown()
                
                /**
                * clearInterruptsForTaskRun operation
                * Ensure that the interrupt flag is set only when the thread is stopping, otherwise clear the interrupt flag
                * 1,If the thread pool status > = stop and the current thread has no interrupt status set, wt.interrupt()
                * 2,If the thread pool state is judged to be < stop at the beginning, but thread Interrupted() is true, that is, the thread has been interrupted, and the interrupt flag is cleared. Judge whether the thread pool status is > = stop again
                * Yes, set the interrupt flag again, wt.interrupt()
                * No, do not operate. Clear the interrupt mark and then proceed to the next steps
                */
                if ((runStateAtLeast(ctl.get(), STOP) ||
                        (Thread.interrupted() &&
                                runStateAtLeast(ctl.get(), STOP))) &&
                        !wt.isInterrupted())
                    wt.interrupt(); //The current thread calls interrupt() to interrupt

                try {
                    //Before execution (subclass Implementation)
                    beforeExecute(wt, task);

                    Throwable thrown = null;
                    try {
                        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 {
                        //After execution (subclass Implementation)
                        afterExecute(task, thrown); 
                        //Here we will test the execution order of catch and finally, because we need to take throw as the parameter
                    }
                } finally {
                    task = null; //Set task to null
                    w.completedTasks++; //Number of completed tasks + 1
                    w.unlock(); //Unlock
                }
            }

            completedAbruptly = false;
        } finally {
            //Handle worker exit
            processWorkerExit(w, completedAbruptly);
        }
    }
}

runWorker(Worker w)

Execution process:

1. After the Worker thread is started, runWorker(this) is called through the run() method of the Worker class

2. Before executing the task, first the worker Unlock(), set the AQS state to 0, allowing the current worker thread to be interrupted

3. Start executing firstTask and call task Run(), lock the wroker before executing the task Lock() will be unlocked after the task is executed. In order to prevent the task from being interrupted by some interrupt operations of the thread pool when it is running

4. Before and after the task is executed, the beforeExecute() and afterExecute() methods can be customized according to the business scenario

5. Whether in beforeExecute(), task Abnormal throwing up of run() and afterExecute() will cause the worker thread to terminate and enter processWorkerExit() to handle the process of worker exit

6. If the current task is executed normally, a new task will be obtained from the blocking queue through getTask(). When there is no task in the queue and the acquisition task times out, the current worker will also enter the exit process

getTask(): Get task

  /**
     *null will be returned if
     * 1. The number of threads set by maximumPoolSize was exceeded (because setMaximumPoolSize() was called)
     * 2. Thread pool stop ped
     * 3. The thread pool is shut down and the workQueue is empty
     * 4. Thread waiting task timeout
     *
     * @return Returning null indicates that the worker is about to end. In this case, workerCount-1
     */
    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        /**
         * Outer circulation
         * Used to judge the status of thread pool
         */
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            /**
             * For the judgment of thread pool status, workerCount-1 and null will be returned in both cases
             * The thread pool status is shutdown and the workQueue is empty (the thread pool reflecting the shutdown status still needs to execute the remaining tasks in the workQueue)
             * The thread pool status is stop (shutdown now() will cause it to become stop) (do not consider workQueue at this time)
             */
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount(); //Cyclic CAS reduces the number of worker s until success
                return null;
            }

            boolean timed;      // Are workers subject to culling?
            // Whether it is necessary to obtain data from workQueue regularly

            /**
             * inner loop 
             * Or break to the workQueue to get the task
             * Or timeout, worker count-1
             */
            for (;;) {
                int wc = workerCountOf(c);
                timed = allowCoreThreadTimeOut || wc > corePoolSize; //allowCoreThreadTimeOut defaults to false
                //If allowCoreThreadTimeOut is true, it means that both corePoolSize and maximum need timing

                //If the current number of execution threads is < maximumpoolsize, and either timedOut or timed is false,
                // Jump out of the loop and start getting tasks from workQueue
                if (wc <= maximumPoolSize && ! (timedOut && timed))
                    break;

                /**
                 * If this step is reached, it indicates that either the number of threads exceeds the maximumPoolSize (perhaps the maximumPoolSize has been modified)
                 * Either the timer needs to be timed with timed==true or timed out with timedOut==true
                 * worker The number is - 1, minus one execution, and then null is returned. There will be logic to reduce worker threads in runWorker()
                 * If subtracting one fails this time, continue the inner loop and try subtracting one again
                 */
                if (compareAndDecrementWorkerCount(c))
                    return null;

                //If the reduction fails, read the ctl again
                c = ctl.get();  // Re-read ctl

                //If the running state of the thread pool changes, continue the outer loop
                //If the state does not change, continue the inner loop
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }

            try {
                //poll() - use locksupport parkNanos(this, nanosTimeout) 
                // Suspend for a period of time. When interrupt(), no exception will be thrown, but there will be an interrupt response
                //take() - use locksupport Park (this) is suspended. No exception will be thrown during interrupt(), but there will be an interrupt response
                Runnable r = timed ?
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :    
                        //Greater than corePoolSize
                        workQueue.take();                                        
                        //Less than or equal to corePoolSize

                //If the task is obtained, return
                if (r != null)
                    return r;

                //If there is no return, it means timeout. Then the next inner loop will enter the step of worker count minus one
                timedOut = true;
            }
            /**
             * blockingQueue take() blocking using locksupport Park (this) enters the wait state,
             * Yes, locksupport Interrupt by Park (this) will not throw exceptions, but there will still be an interrupt response
             * However, the await() of the ConditionObject of AQS judges the interrupt status and will report the interrupt status 
             * reportInterruptAfterWait(interruptMode)
             * The InterruptedException will be thrown up, captured here and the loop will be restarted
             * If the idle worker interrupt response is caused by operations such as shutdown(), it may return null when the outer loop judges the state
             */
            catch (InterruptedException retry) {
                timedOut = false; //Respond to the interrupt and restart, and the interrupt status will be cleared
            }
        }
    }

Execution process:

1. First, judge whether the conditions for obtaining tasks from workQueue can be met. If not, return null

A. whether the thread pool status meets the following requirements:

(a) the shutdown status + workQueue is empty or the stop status is not satisfied, because after being shut down, the remaining tasks in the workQueue will still be executed, but the workQueue is also empty, so you can exit

(b) stop status. The shutdown now() operation will cause the thread pool to enter stop. At this time, it will not accept new tasks, interrupt the executing tasks, and the tasks in the workQueue will not be executed, so return null returns

B. whether the number of threads exceeds maximumPoolSize or whether the acquisition task times out

(a) if the number of threads exceeds maximumPoolSize, it may be that the thread pool is called during runtime and setMaximumPoolSize() is changed in size. Otherwise, addWorker() has succeeded and will not exceed maximumPoolSize

(b) if the current number of threads > corePoolSize, it will check whether to get the task timeout. This also reflects that when the number of threads reaches maximumPoolSize, if there is no new task, the worker thread will be terminated gradually until corePoolSize

2. If the acquisition task conditions are met, call different methods according to whether it is necessary to acquire regularly:

    A,workQueue.poll(): if there is still no task in the blocking queue within the keepAliveTime time, null is returned

    B,workQueue.take(): if the blocking queue is empty, the current thread will be suspended for waiting; When a task is added to the queue, the thread is awakened and the take method returns the task

3. When blocking the acquisition of tasks from workQueue, it can be interrupted by interrupt(). The code captures the InterruptedException, resets timedOut to the initial value false, and executes the judgment in step 1 again. If it is satisfied, it will continue to obtain tasks. If it is not satisfied with return null, it will enter the worker exit process

Difference between Worker and Task:

The Worker is a thread in the thread pool. Although the Task is runnable, it is not actually executed, but the Worker calls the run method. You will see the implementation of this part later.

processWorkerExit():worker thread exits

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    /**
     * 1,worker Quantity - 1
     * If it is terminated suddenly, it indicates that it is caused by an exception during task execution, that is, an exception occurred during the execution of the run() method,
     *The number of working worker threads needs to be - 1
     * If it is not terminated suddenly, it means that the worker thread has no task to execute, so - 1 is not needed, because - 1 is already in the getTask() method
     */
    if (completedAbruptly) 
        // If abort, then workercount was't adjusted
        decrementWorkerCount();
 
    /**
     * 2,Remove worker from Workers Set
     */
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks; //Add the number of tasks completed by the worker to the number of tasks completed by the thread pool
        workers.remove(w); //Remove from HashSet < worker >
    } finally {
        mainLock.unlock();
    }
 
    /**
     * 3,When there is a negative effect on the thread pool, you need to "try to terminate" the thread pool
     * It is mainly to judge whether the thread pool meets the termination status
     * If the status is satisfied, but there are still threads in the thread pool, try to send an interrupt response to them to enable them to enter the exit process
     * There are no threads. The update status is tidying - > terminated
     */
    tryTerminate();
 
    /**
     * 4,Do you need to add worker threads
     * The thread pool status is running or shutdown
     * If the current thread terminates suddenly, addWorker()
     * If the current thread does not terminate suddenly, but the number of current threads is less than the number of threads to be maintained, addWorker()
     * Therefore, if the thread pool shutdown() is called until the workQueue is unprecedented, the thread pool will maintain corePoolSize threads, and then gradually destroy these corePoolSize threads
     */
    int c = ctl.get();
    //If the status is running or shutdown, that is, tryTerminate() failed to terminate the thread pool, try adding another worker
    if (runStateLessThan(c, STOP)) {
        //If it is not completed suddenly, i.e. no task task can be obtained, calculate min and judge whether addWorker() is required according to the current number of workers
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize; //allowCoreThreadTimeOut defaults to false, that is, min defaults to corePoolSize
             
            //If min is 0, there is no need to maintain the number of core threads, and workQueue is not empty. At least one thread should be maintained
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
             
            //If the number of threads is greater than the minimum number, return directly. Otherwise, at least one addWorker is required below
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
         
        //Add a worker without firstTask
        //As long as the worker is terminated abruptly or the number of threads is less than the number to be maintained, a new worker thread will be added, even in the shutdown state
        addWorker(null, false);
    }
}

processWorkerExit(Worker w, boolean completedAbruptly)

Worker: the worker to end

completedAbruptly: whether it is completed suddenly (whether it exits due to exception)

Execution process:

1. Number of worker s - 1

A. if it is terminated suddenly, it means that it is caused by an exception during task execution, that is, an exception occurs during the execution of the run() method, so the number of worker threads working needs to be - 1

B. if it is not terminated suddenly, it means that the worker thread has no task to execute, so - 1 is not needed, because - 1 is already in the getTask() method

2. To remove a worker from the Workers Set, lock mainlock when deleting

3. tryTerminate(): you need to "try to terminate" the thread pool when there are negative effects on the thread pool. The approximate logic is as follows:

Judge whether the thread pool meets the termination status

A. if the status is satisfied, but there are still threads in the thread pool, try to send an interrupt response to them so that they can enter the exit process

B. There are no threads, and the update status is tidying - > terminated

4. Whether worker threads need to be added. If the thread pool has not been completely terminated, a certain number of threads still need to be maintained

The thread pool status is running or shutdown

A. if the current thread terminates suddenly, addWorker()

B. if the current thread does not terminate suddenly, but the number of current threads is less than the number of threads to be maintained, addWorker()

Therefore, if the thread pool shutdown() is called until the workQueue is unprecedented, the thread pool will maintain corePoolSize threads, and then gradually destroy these corePoolSize threads

ThreadPoolExecutor thread pool termination

There are two main methods to terminate the thread pool: shutdown() and shutdown now().

shutdown method

After shutdown(), the thread pool will change to the shutdown state. At this time, new tasks will not be received, but the running and waiting tasks in the blocking queue will be processed.

/**
     * Start an orderly shutdown. In the shutdown, the previously submitted tasks will be executed (including those in execution and in the blocking queue), but the new tasks will be rejected
     * If the thread pool has been shut down, calling this method will have no additional effect
     * <p>
     * The current method will not wait for the completion of the previously submitted task. You can use awaitTermination()
     */
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock(); //Lock

        try {
            //Determine whether the caller has permission to shut down the thread pool
            checkShutdownAccess();

            //CAS + loop sets the thread pool status to shutdown
            advanceRunState(SHUTDOWN);

            //Interrupt all idle threads
            interruptIdleWorkers();

            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock(); //Unlock
        }

        //Attempt to terminate thread pool
        tryTerminate();
    }

shutdown() execution process:

1. Lock. mainLock is the main lock of the thread pool. It is a reentrant lock. When you want to operate workers set, which is a HashSet that holds threads, you need to obtain mainLock first. When you want to process statistics such as largestPoolSize and completedTaskCount, you need to obtain mainLock first

2. Determine whether the caller has permission to shut down the thread pool

3. Use CAS operation to set the thread pool status to shutdown. After shutdown, no new tasks will be received

4. Interrupt all idle threads ()

5. onShutdown(), which is implemented in ScheduledThreadPoolExecutor, can do some processing during shutdown()

6. Unlock

7. Attempt to terminate thread pool {tryTerminate()

You can see that the most important steps of the shutdown() method are: updating the thread pool status to shutdown, interrupting all idle threads, and tryTerminated() trying to terminate the thread pool

interruptIdleWorkers() interrupts idle threads as follows:

    /**
     * Interrupt the thread waiting for the task (unlocked). After the interrupt wakes up, you can judge whether the thread pool state changes to decide whether to continue
     * <p>
     * onlyOne If true, interrupt one worker at most * only when the termination process has started,
     * However, when there are worker threads in the thread pool, the tryTerminate() method will call onlyOne to true
     * (The termination process has started, which means: shutdown status and workQueue is empty, or stop status)
     * In this case, at most one worker is interrupted in order to propagate the shutdown signal so that all threads are not waiting
     * To ensure that the thread pool can eventually terminate, this operation always interrupts an idle worker
     * shutdown() interrupts all idle worker s to ensure that idle threads exit in time
     */
    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock(); //Lock 
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne) break;
            }
        } finally {
            mainLock.unlock(); //Unlock}}
        }
    }

First, obtain the mainLock lock. To iterate over the workers set, you need to make two judgments before interrupting each worker:

1. Whether the thread has been interrupted or not is to do nothing

2,worker. Is trylock() successful

The second judgment is important because the Worker class not only implements the executable Runnable, but also inherits the AQS. It is also a lock. tryLock() calls the tryAcquire() method implemented by the Worker itself, which attempts to obtain exclusive resources. If the acquisition is successful, it will directly return true. Otherwise, it will directly return false. This is also the method that the subclass of AQS needs to implement to try to obtain the lock

protected boolean tryAcquire(int unused) {
	if (compareAndSetState(0, 1)) {
		setExclusiveOwnerThread(Thread.currentThread());
		return true;
	}
	return false;
}

tryAcquire() first attempts to change the state of AQS from 0 -- > 1, and returns true to indicate successful locking, and sets the current thread as the owner of the lock. You can see that compareAndSetState(0, 1) only tries to acquire the lock once, and instead of state+1 each time, it is 0 -- > 1, indicating that the lock is not reentrant.

worker.tryLock() get worker lock

Woker class can control thread interruption, so every time task is obtained in runWorker() method, task Worker is required before run() Lock() is locked and unlocked after running, that is, the working threads running tasks are locked by worker

Shutdown now method

After shutdown now(), the thread pool will change to stop status. At this time, it will not receive new tasks, will not process tasks waiting in the blocking queue, and will also try to interrupt the working thread in process.

    /**
     * Try to stop all active executing tasks, stop the processing of waiting tasks, and return to the list of tasks waiting to be executed
     * This task list is discharged (deleted) from the task queue
     * <p>
     * This method does not have to wait for the end of the task being executed. To wait for the termination of the thread pool, you can use awaitTermination()
     * <p>
     * There is no guarantee other than trying to stop the running task
     * Cancel the task through thread Interrupt () is implemented, so any task that fails to respond to an interrupt may never end
     */
    public List <Runnable> shutdownNow() {
        List <Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock(); //Lock

        try {
            //Determine whether the caller has permission to shut down the thread pool
            checkShutdownAccess();

            //CAS + loop sets the thread pool status to stop
            advanceRunState(STOP);

            //Interrupt all threads, including those of running tasks
            interruptWorkers();

            tasks = drainQueue(); 
            //Put the elements in workQueue into a List and return
        } finally {
            mainLock.unlock(); 
            //Unlock
        }

        //Attempt to terminate thread pool
        tryTerminate();

        return tasks; 
        //Return unexecuted tasks in workQueue
    }

The general processes of shutdown now() and shutdown() are similar, with the following differences:

1. Update thread pool to stop status

2. Call interruptWorkers() to interrupt all threads, including running threads

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
        }finally {
        mainLock.unlock();
    }
}

interruptWorkers lock mainLock first, and then call the interruptIfStarted method to interrupt all threads of the worker. It will judge whether the AQS state of the worker is greater than 0, that is, whether the worker has started to operate, and then call thread interrupt(). It should be noted that thread. Is called for the running thread Interrupt () does not guarantee that the thread will be terminated, task The interrupt exception may be caught inside run() and not thrown up, which makes the thread unable to end all the time

3. Move the tasks to be processed in the workQueue to a List and return at the end of the method, indicating that the tasks in the workQueue will not be processed after shutdown now()

 

Topics: Java Back-end