ThreadPoolExecutor thread pool management

Keywords: Java

1. Introduction

It is an important class in the thread pool. It is associated with the Executor and can be said to replace the Executor. Used to create a thread pool to handle high concurrency.

2. Principle

In fact, the implementation principle of java thread pool is very simple. To put it bluntly, it is a thread set workerSet and a blocking queue workQueue. When a user submits a task (that is, a thread) to the thread pool, the thread pool will first put the task into the workQueue. The threads in the workerSet will continuously obtain threads from the workQueue and then execute. When there are no tasks in the workQueue, the worker will block until there are tasks in the queue, and then take them out to continue execution.

After a task is submitted to the thread pool:

  1. The thread pool first determines whether the number of threads currently running is less than corePoolSize. If so, create a new worker thread to execute the task. If they are all performing tasks, enter 2
  2. Judge whether the BlockingQueue is full. If not, put the thread into the BlockingQueue. Otherwise, enter 3
  3. If creating a new worker thread will make the number of currently running threads exceed maximumPoolSize, it will be handed over to RejectedExecutionHandler to process the task.

When ThreadPoolExecutor creates a new thread, it updates the status ctl of the thread pool through CAS
This logic can be seen in the execute method in the source code

The specific logic of this class is all in the source code. You can read it patiently, which will be helpful to you

3. Source code

Important parameters

//ctl is an important variable used to store the number of currently running workers and the status of thread pool. Int is 32 bits. Here, the upper 3 bits of int are used as the flag bit of thread pool status, and the last 29 bits are used as the number of currently running workers
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;

   //The following one is the value of the runstate variable, which indicates the running state of the thread
    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;
 //Blocking queue   
private final BlockingQueue<Runnable> workQueue;
//Define a ReentrantLock object to re-enter the lock
private final ReentrantLock mainLock = new ReentrantLock();
//Worker collection, which represents the running thread collection
private final HashSet<Worker> workers = new HashSet<Worker>();
//Inherited AQS, the condition queue inside
private final Condition termination = mainLock.newCondition();
//Maximum number of threads stored in the pool
private int largestPoolSize;
//Number of tasks completed
private long completedTaskCount;
//Thread factory, used to create threads
private volatile ThreadFactory threadFactory;
//The processor can customize the processing when the pool thread is saturated and a new task comes in. The default is to throw an exception
private volatile RejectedExecutionHandler handler;
//Thread lifetime
private volatile long keepAliveTime;
//Core thread timeout continue available
private volatile boolean allowCoreThreadTimeOut;
//Number of core threads
private volatile int corePoolSize;
//Maximum threads in pool
private volatile int maximumPoolSize;
//The default processor throws an exception directly
private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

CAS method

private boolean compareAndIncrementWorkerCount(int expect) {
        return ctl.compareAndSet(expect, expect + 1);
    }

    private boolean compareAndDecrementWorkerCount(int expect) {
        return ctl.compareAndSet(expect, expect - 1);
    }

    private void decrementWorkerCount() {
        do {} while (! compareAndDecrementWorkerCount(ctl.get()));
    }

It can be seen that ctl variables are operated through CAS

One of the construction methods

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

There's nothing to say

Inner class Worker

  private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        private static final long serialVersionUID = 6138294804551838833L;

        final Thread thread;  

        Runnable firstTask;

        volatile long completedTasks;  //volatile is used to achieve visibility

        Worker(Runnable firstTask) {
            setState(-1); 
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);  //It can be seen from here that the Worker class implements the Runnable interface. When creating a new thread, the Worker object can be passed as a parameter, which can be regarded as the Runnable interface (a task)
        }

        public void run() {
            runWorker(this);  //Run Worker object
        }
     //Is the inherited AQS synchronizer an exclusive lock
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }
    //Inherited AQS synchronizer, trying to acquire lock
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
//Inherited AQS synchronizer, trying to release the lock
        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(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

It inherits AQS and implements the Runnable interface. It can deduce that the Worker object can be used as a task to transfer parameters.

Common method

//This method is to set the thread to its desired state targetState
private void advanceRunState(int targetState) {
        for (;;) {  //Infinite loop
            int c = ctl.get();  //Get the current status value
            if (runStateAtLeast(c, targetState) ||
                ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))  //CAS
                break;
        }
    }
    
final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;  //Get reentry lock
            mainLock.lock();  //Lock
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {  //Set the status to TIDYING
                    try {
                        terminated();  //termination
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));  //Set the status to TERMINATED
                        termination.signalAll();  //Wake up the thread in the blocking queue
                    }
                    return;
                }
            } finally {
                mainLock.unlock();  //Release lock
            }
        }
    }
    
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;  //Get reentry lock
        mainLock.lock();  //Lock
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
    
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

Core method

//This method is used to interrupt all starting workers
private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

//Interrupt all idle workers
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();  //interrupt
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();  /Release lock
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
    
    //Reject operation when thread pool is saturated
    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }
    
    //Add worker threads to the pool. This core indicates whether the number of threads is the core thread or the maximum thread. This method is to add and execute workers
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {  //Infinite loop
            int c = ctl.get();  //Get ctl
            int rs = runStateOf(c);  //Get the running status value from ctl

            //Check whether the blocking queue is empty
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {  //Infinite loop
                int wc = workerCountOf(c);  //Get the number of worker s from ctl
                //Judge whether the work quantity exceeds the allowable capacity
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //Increase workerCount by one with CAS
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //Judge the running state
                c = ctl.get();  
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

       //These are two switch properties
        boolean workerStarted = false;  //Does the worker start
        boolean workerAdded = false;  //Whether the worker was successfully added
        Worker w = null;
        try {
            w = new Worker(firstTask);  //Initialize worker object
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;  //Gain reentry lock
                mainLock.lock();  //Lock
                try {
                 
                    int rs = runStateOf(ctl.get());  //Get running status

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);  //Add the worker object to the workerSet collection
                        int s = workers.size();  //Get the size of the workerSet collection
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;  //Set the switch property to true, indicating that the addition is successful
                    }
                } finally {
                    mainLock.unlock();  //Unlock
                }
                if (workerAdded) {
                    t.start();  //Start executing worker
                    workerStarted = true;  //Set the switch property to true to start execution
                }
            }
        } finally {
            if (! workerStarted)  //Determine whether the worker has actually started execution
                addWorkerFailed(w);  //If not, call the addWorkerFailed method
        }
        return workerStarted;  //Returns the Boolean value of whether to start execution
    }
    
    //This method is executed after adding a worker fails
    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;  //Reentry lock
        mainLock.lock();  //Lock
        try {
            if (w != null)
                workers.remove(w);  //Remove this worker from the workerSet
            decrementWorkerCount();  //Subtract one from workercount, CAS operation
            tryTerminate();  //Terminate current worker
        } finally {
            mainLock.unlock();  //Unlock
        }
    }	
    
    //This method is used to handle the worker exit logic
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;  //Reentry lock
        mainLock.lock();  //Lock
        try {
            completedTaskCount += w.completedTasks;  //Add the number of tasks completed by this worker to the completedTaskCount
            workers.remove(w);  //Quit again
        } finally {
            mainLock.unlock();  //Unlock
        }

        tryTerminate();  //Then terminate the worker

        int c = ctl.get();  //Get status value
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }
    
    //This method is used to get the task
        private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {  //Infinite loop
            int c = ctl.get();  
            int rs = runStateOf(c);  //Get running status

            //Check whether the blocking queue is empty
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);  //Get number of worker threads

            // Check again for timeout
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

           //Determine whether the number of worker threads overflows and recheck timeout
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
            //Check the timeout again. If there is no timeout, get a task from workQueue
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;  //Return to this task
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
    
    //Run worker
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();  //Get current thread
        Runnable task = w.firstTask;  //Get the task
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock(); //Lock
                
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);  //Operations before execution, life cycle hook
                    Throwable thrown = null;
                    try {
                        task.run();  //Run task
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);  //Operation after execution, life cycle hook
                    }
                } finally {
                    task = null;  //After the task is executed, it is set to null for the convenience of GC
                    w.completedTasks++;  //Number of tasks completed at the same time plus one
                    w.unlock();  //Release lock
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);  //Finally, execute the exit worker operation
        }
    }
    
    //The overall logic of addWorker called in execution is to judge the relationship between the number of threads and the number of core threads and the maximum number of threads, and determine how the worker should be created or reject ed through this relationship
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
       
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

3. Summary

1. ThreadPoolExecutor is used to perform logical processing of tasks accepted by the thread pool and thread execution. If you want to compare with Executor, Executor is a large range. It does not consider the processing logic inside the thread pool, but can implement business logic and create a thread pool that conforms to business logic; The ThreadPoolExecutor is a small scope, which can be said to be the internal implementation of the Executor. It focuses on the code logic within the thread pool, thread scheduling and task scheduling.

2. ThreadPoolExecutor can also be used in multi-threaded environment. Its method uses ReentrantLock to add and release locks. At the same time, the ctl attribute uses CAS atomic operation to ensure data consistency; In addition, volatile keyword is used to modify attributes in a few places to ensure the visibility of attributes.

3. The method in ThreadPoolExecutor basically judges the condition based on the attribute ctl. The upper three bits of ctl are the saved RunState, and the lower 29 bits are the number of threads in the thread pool

Posted by quartney on Fri, 24 Sep 2021 19:57:36 -0700