ThreadPoolExecutor source code reading

1. Key attributes

/** This property is very interesting. A synchronous Integer stores two values, with a maximum of 3 bits to place the thread pool state; The remaining 29 places the number of workers, so there can be at most 2 ^ 29-1 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;
/** You can see that the top three thread pool states have values, and the other bits are 0 */
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;
/** Get the thread pool status, reverse the capability and follow the c bit by bit. Oh, roar will come out */
private static int runStateOf(int c)     { return c & ~CAPACITY; }
/** Get the number of worker s in the thread pool. The CAPACITY and c are matched by bits. Oh, roar will come out */
private static int workerCountOf(int c)  { return c & CAPACITY; }
/** Obtain ctl data. rs is the status and wc is the number of worker s */
private static int ctlOf(int rs, int wc) { return rs | wc; }
/** Wait queue. If the conditions are not met when obtaining threads from the thread pool, add them here and wait */
private final BlockingQueue<Runnable> workQueue;
/** worker Queue. This queue is actually a pool. Threads are maintained in the worker, and they need to be obtained here. So why package it with worker? See the worker section for details */
private final HashSet<Worker> workers = new HashSet<Worker>();
/** Reentrant lock is used to lock important code. Workers and largestPoolSize are locked in addWorker, which is easy to understand because workers are not thread safe; largestPoolSize also needs to be changed, so it must be locked */
private final ReentrantLock mainLock = new ReentrantLock();
private int largestPoolSize;
/** Survival time, non core thread, is recycled if it is useless beyond this time */
private volatile long keepAliveTime;
/** Number of core threads */
private volatile int corePoolSize;
/** Maximum number of threads */
private volatile int maximumPoolSize;

         How are corePoolSize, maximumPoolSize and workQueue matched? At first, the thread pool was empty. Later, a task was submitted. At this time, you need to build a core thread. After the core thread is created to the corePoolSize, add it to the workQueue to make the task wait. If the workQueue is also full, start creating non core threads until the maximum poolsize is reached. Again, you have to reject the task according to the rejection strategy.

         Why is ReentrantLock mainLock a reentrant lock? I understand that it is necessary to repeatedly lock when adding a worker or tryTerminate. For example, when addWorkerFailed, there will be a lock in addWorkerFailed. Before releasing the lock, tryTerminate will lock again. If it is non reentrant, it is locked.

2. Inner class - worker

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }
    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    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) {
            }
        }
    }
}

         Worker is an interesting internal class, which is responsible for executing externally submitted tasks (implemented run methods). Continue to look at worker. He inherited AbstractQueuedSynchronizer. He is actually a lock! Take a further look at tryLock, combined with AbstractQueuedSynchronizer (AQS) JDK concurrent programming core_ Blog of workhardliuzheng - CSDN blog Look, he's still a non reentrant lock. Then why design the Worker as a lock. According to the comments, it is to maintain the thread interrupt state. Well, look where to add the lock and try to get the lock. You can see that the lock only calls runWorker in one place and locks it when running the task; When do you try to acquire a lock? Interrupt idleworkers acquire a lock when trying to interrupt. The combination is that the thread cannot be interrupted when running, so lock it before running; Before interruption, you need to try to obtain the lock. If you can't obtain it, you can never interrupt.

         At the same time, the worker is also a delegate class executed by the thread, and all runable s are delegated to the worker for execution. I think it can be understood that all "living" threads in the thread pool are threads held by the worker. These threads may be core or non core.

3. Key methods

execute method

/** The specific logic of the methods we execute most is actually the above-mentioned. How do we match the parameters */
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
	/** If it is now smaller than the core thread, add the number of cores */
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
	/** If you can get here, you can either exceed the number of core threads or try to join the queue without adding worker s */
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
		/** Check again here, because the isRunning and offer above are time-consuming, and the status may have changed after reaching this step */
        if (! isRunning(recheck) && remove(command))/** If it is not running, then remove the command. If the removal is successful, reject it */
            reject(command);
        else if (workerCountOf(recheck) == 0) 
            addWorker(null, false);
    }
    else if (!addWorker(command, false)) /** You must add a non core thread here, because the queue must be full, so you have no idea to add addWorker first. If you still fail to join, you have to refuse */
        reject(command);
}

addWorker method

/** Add worker to worker's set */
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
	/** Because there is CAS in it, it must be closed-loop */
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        /** If the thread is not running and rs is not equal to SHUTDOWN and firstTask is not equal to empty and
         *    workQueue Null, return false directly
         *    1. After the thread pool has been shut down, add a new task and reject it
         *    2. (The second judgment) SHUTDOWN status does not accept new tasks, but still executes tasks that have been added to the task queue
         *    Therefore, when it enters the SHUTDOWN state, and the transmitted task is empty, and the task queue is not empty, it is allowed to add
         *   For new threads, if this condition is reversed, it means that adding worker s is not allowed
		 */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        for (;;) {
            int wc = workerCountOf(c);
			/** If the maximum capacity of the thread pool is exceeded, or the core or maximum number of threads is exceeded (related to whether the new worker is core or non core), it is not allowed to increase the worker */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
			/** Increase the number of worker s. Once CAS is successfully increased, it will jump out directly and no longer enter the cycle */
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
			/** You need to re verify the current state of the thread pool. If the state has changed, you need to jump out and enter the cycle again to verify whether it is necessary to add a new thread pool */
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
			/** Lock the hashset of workers and protect largestPoolSize */
            mainLock.lock();
            try {
                /** Recheck while holding the lock. Exits when ThreadFactory fails or closes before acquiring a lock. */
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
				/** Here t is the thread in the worker. After the thread starts, it will call the run method of the worker, and then enter runWorker to start execution */
                t.start();
                workerStarted = true;
            }
        }
    } finally {
		/** If the worker is not started, you need to reduce the count of the worker and enter tryTerminate to try to reduce the number of idle workers (if not started, of course) */
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

tryTerminate method

/** Try to terminate the thread pool. Only the thread pool is shutdown and the worker and queue are empty, or the thread pool is stop and the worker is empty */
final void tryTerminate() {
	/** For dead loop, ctl.compareAndSet(c, ctlOf(TIDYING, 0)) must be set successfully, otherwise it will wait forever */
    for (;;) {
        int c = ctl.get();
		/** If the thread pool is running, or the status is TIDYING, or the status is terminated, or the status is shutdow n, but the queue is not empty. You can't stop at this time, so return directly
		* If the queue is empty, it may be terminated, but if it is not empty, it must not be terminated.
		*/
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
		/** If there is a worker, it cannot be terminated, but an idle worker needs to be interrupted to allow the semaphore to be transmitted
		* This sentence is written on the notes, which is very puzzling...
		*/
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
			/** After the interruption, only one idle worker is interrupted. Of course, there may not be any idle workers. Then you need to exit, let the external method enter tryTerminate again, and check again whether all workers have stopped */
            return;
        }
		/** Get global lock */
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {/** CAS Set thread pool to be terminated */
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {/** Termination method of subclass implementation */
                    terminated();
                } finally {
					/** After the subclass is executed, set the thread pool status to terminate */
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

         But you need to interrupt an idle worker to let the semaphore pass out. This sentence is very puzzling.... To understand this sentence, you need to combine it with the getTask method. getTask is a method to get a task from the blocking queue and execute it in runWorker. From the following code

Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();

         When the queue is empty, it will block there. Then it is clear that the worker has several states: 1. Running; 2. The queue is empty, blocking or the worker has not obtained the task (the worker is idle). If interruptIdleWorkers interrupt an idle worker, an interrupt exception will be thrown to free the getTask from blocking. After being captured externally, it will enter the getTask again. The first step in entering the getTask is to check whether the thread pool is shutdown and the queue is empty. If it is empty, a null is returned to the runWoker method. At this time, runWorker exits the loop and enters the final processWorkerExit - worker exit action. Go back to tryTerminate and return after interrupting a worker. Because I don't know whether there are workers after the interruption, I need to call tryTerminate to check whether it can be interrupted again until all workers are terminated.

         runWorker and getTask are mentioned above. Let's take a look at these two parts of code

runWorker method

/** Execute the worker method, and the run method of the worker class will call this method */
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
	/** Get the task in the worker */
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
		/** There are two steps in this judgment. If the bound task is not empty, it will directly enter the loop; If it is empty, get the task in the queue through getTask and assign it to task,
		* If the data is obtained, it will enter the loop. If it is not obtained, it will enter the final worker termination method 
		*/
        while (task != null || (task = getTask()) != null) {
			/** Lock the worker first. The worker is already running
 No, no occupation, no interruption */
            w.lock();
			/** If the pool is stopping, ensure that the thread is interrupted; If not, ensure that the thread is not interrupted. This requires a secondary check to prevent shutdown now when the interrupt is cleared */
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
				/** Subclass implementation, pre execution logic */
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {/** Perform specific 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 {/** Subclass implementation, post execution logic */
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;/** Either the task is completed or the exception is thrown. Anyway, this round of task is completed and the task is set to empty */
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
		/** This worker needs to be terminated after exiting */
        processWorkerExit(w, completedAbruptly);
    }
}

getTask method

/** Get a task from the blocking queue */
private Runnable getTask() {
    boolean timedOut = false; 
	/** Keep looping until the task is obtained or not satisfied, directly return null and let runWorker exit the worker */
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        /** If the thread pool is ready to terminate, and the thread pool is stop or the waiting queue is empty */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
			/** worker The number is reduced by one and null is returned because the thread pool is ready to terminate and the worker needs to be terminated */
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
		/** Check whether you need to reduce one worker and meet two conditions
		* 1,Either there is a non core worker, or the core worker is timeout.
		* 2,If 1 is satisfied, we need to see if we can reduce one worker, because either the queue is empty, we can reduce it directly, or if the queue is not empty, we have to reserve one worker to consume the queue
		*/
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
		/** Getting data from the queue is either a blocking acquisition without waiting time or a blocking acquisition with waiting time */
        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

Tags: Java

Posted on Fri, 03 Dec 2021 18:40:40 -0500 by ElkySS