ThreadPoolExecutor Source Analysis for Java Concurrent Package Source Learning ThreadPools

Keywords: less Java JDK

Original Link: https://my.oschina.net/u/1052976/blog/550068

Thread pooling technology in Java typically uses Executors, a factory class that provides a very simple way to create various types of thread pools:

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor() 
public static ExecutorService newCachedThreadPool() 
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

The core interface is Executor, which has only one execute method abstracted as execution of tasks (Runnable interface). ExecutorService interface provides life cycle management of task execution based on Executor, mainly submit and shutdown methods. AbstractExecutorService has some parties to ExecutorServiceMethod is implemented by default, mainly submit and invoke methods. Executor interface execute method for true task execution is implemented by subclass, ThreadPoolExecutor, which implements the task execution framework based on thread pool, so to understand the thread pool of JDK, you must first look at this class.

Before looking at the execute method, you need to introduce several variables or classes.

 

ctl

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

This variable is the core of the whole class. AtomicInteger guarantees that manipulation of this variable is atomic. ThreadPoolExecutor uses this variable to save two things with clever manipulation:

  • Number of all valid threads
  • Status of individual threads (runState)

Low 29-bit memory threads, high 3-bit memory runState, so runState has five values:

  • RUNNING:-536870912
  • SHUTDOWN:0
  • STOP:536870912
  • TIDYING:1073741824
  • TERMINATED:1610612736

The transition between states in a thread pool is complex. Just remember the following:

  • RUNNING status: The thread pool is functioning properly and can accept new tasks and process tasks in the queue;
  • SHUTDOWN status: No longer accepts new tasks, but executes tasks in the queue;
  • STOP Status: No more new tasks are accepted, no tasks in the queue are processed

There are operations around the ctl variable, and understanding these methods is the basis for understanding some of the obscure code that follows:

/**
 * This method is used to take out the runState value because the CAPACITY value is: 00011111111111111111111
 * ~For bitwise inversion, ~CAPACITY is 11100000000000000000000000000000
 * If you do this with the same parameter, the position will be 0 lower than 29, and the value of runState will remain the same 3 digits higher.
 * 
 * @param c
 *            This parameter is the int value that stores runState and workerCount
 * @return runState Value of
 */
private static int runStateOf(int c) {
    return c & ~CAPACITY;
}


/**
 * This method is used to take out the value of workerCount
 * Since the CAPACITY value is: 00011111111111111111111111 11, &the operation will set the parameter at the top 3 position of 0
 * Keep the low 29 bits of the parameter, which is the value of the workerCount
 * 
 * @param c
 *            ctl, Store run state and workerCount int values
 * @return workerCount Value of
 */
private static int workerCountOf(int c) {
    return c & CAPACITY;
}

/**
 * Save runState and workerCount in the same int
 * "|"The meaning of the operation is that if the value of rs is 101000 and the value of wc is 000111, their bit or operation is 101111
 * 
 * @param rs
 *            runState Shifted values, responsible for filling the high 3 bits of the returned value
 * @param wc
 *            workerCount Shifted value, responsible for filling the lower 29 bits of the returned value
 * @return Both or the calculated value
 */
private static int ctlOf(int rs, int wc) {
    return rs | wc;
}

// only RUNNING The state will be less than 0
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}
View Code

 

corePoolSize

Core thread pool size, active threads less than corePoolSize are created directly, greater than or equal to are added to workQueue first, and queues are full before new threads are created.

 

keepAliveTime

Threads get the task's timeout from the queue, which means they terminate if the thread is idle for more than that time.

 

Worker

private final class Worker extends AbstractQueuedSynchronizer implements Runnable ...

The internal class Worker is an encapsulation of tasks, all submit's Runables are encapsulated as Workers, it's also a Runnable, and then uses the AQS framework (see me about AQS) This article ) A simple, non-reentrant mutex is implemented. The main purpose of mutex is to determine whether a thread is idle or running when it is interrupted. You can see the analysis of shutdown and shutdownNow methods later.

// state Only 0 and 1, mutually exclusive
protected boolean tryAcquire(int unused) {
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;// Lock acquired successfully
    }
    // Threads enter the waiting queue
    return false;
}

protected boolean tryRelease(int unused) {
    setExclusiveOwnerThread(null);
    setState(0);
    return true;
}

ReentrantLock is not used to avoid modifying thread pool variables, such as setCorePoolSize, in the code for task execution because ReentrantLock is reentrant.

 

execute

The execute method consists of three main steps:

  • Create a new thread when the active thread is less than corePoolSize;
  • When the active thread is larger than corePoolSize, it joins the task queue first.
  • The task queue is full before starting a new thread and rejects the task if the maximum number of threads is reached.
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    // Number of active threads < corePoolSize
    if (workerCountOf(c) < corePoolSize) {
        // Start a new thread directly.Second parameter true:addWorker Will be re-checked workerCount Is it less than corePoolSize
        if (addWorker(command, true))
            // Add Successful Return
            return;
        c = ctl.get();
    }
    // Number of active threads >= corePoolSize
    // runState by RUNNING && Queue not full
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // double check
        // wrong RUNNING The state is from workQueue Remove task from and reject
        if (!isRunning(recheck) && remove(command))
            reject(command);// Task rejection using thread pool specified policy
        // Thread pool is in RUNNING state || Thread pool is not RUNNING Status but task removal failed
        else if (workerCountOf(recheck) == 0)
            // This line of code is for SHUTDOWN There are no active threads in the state, but there are tasks in the queue that do not perform this special case.
            // Add one null Tasks are due to SHUTDOWN In state, the thread pool no longer accepts new tasks
            addWorker(null, false);

        // Two cases:
        // 1.wrong RUNNING Status rejects new tasks
        // 2.Queue full failed to start new thread( workCount > maximumPoolSize)
    } else if (!addWorker(command, false))
        reject(command);
}

When the comment is clear, it is no longer explained, and the harder part to understand should be addWorker(null, false); this line, combined with addWorker.The primary purpose is to prevent the HUTDOWN state from losing active threads, but there are tasks in the queue that do not perform this special case.

 

addWorker

This method is hard to understand.

private boolean addWorker(Runnable firstTask, boolean core) {
        retry: for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);// Current Thread Pool Status

            // Check if queue empty only if necessary.
            // This statement is equivalent: rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null ||
            // workQueue.isEmpty())
            // Return directly if the following price adjustments are met false,Thread creation failed:
            // rs > SHUTDOWN:STOP || TIDYING || TERMINATED New tasks are no longer accepted and all tasks are completed
            // rs = SHUTDOWN:firtTask != null Tasks are no longer accepted at this time, but queued tasks are still executed
            // rs = SHUTDOWN:firtTask == null see execute Methodological addWorker(null,
            // false),Task is null && Queue empty
            // The last case is that SHUTDONW In the state, if the queue is not empty, it has to be executed further down. Why? add One null What is the purpose of the task?
            // see execute Method only workCount==0 When firstTask Will be null The condition here is the thread pool SHUTDOWN No more new assignments
            // But at this point the queue is not empty, and you have to create a thread to finish executing the task.
            if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
                return false;

            // When you get here:
            // 1.Thread pool status is RUNNING
            // 2.SHUTDOWN Status, but there are tasks in the queue that need to be executed
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))// Incremental Atomic Operations workCount
                    break retry;// A retry loop that the operation successfully jumped out of
                c = ctl.get(); // Re-read ctl
                if (runStateOf(c) != rs)// Retry if the state of the thread pool changes
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        // wokerCount Incremental Success

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            final ReentrantLock mainLock = this.mainLock;
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                // Concurrent access thread pool workers Object must be locked
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int c = ctl.get();
                    int rs = runStateOf(c);

                    // RUNNING state || SHUTDONW Clean up remaining tasks in queue in state
                    if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // Add a newly started thread to the thread pool
                        workers.add(w);
                        // To update largestPoolSize
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // Start a newly added thread that executes first firstTask,Then keep taking tasks from the queue to execute
                // When waiting keepAlieTime The thread ends without task execution.see runWoker and getTask Code for the method.
                if (workerAdded) {
                    t.start();// The final execution is ThreadPoolExecutor Of runWoker Method
                    workerStarted = true;
                }
            }
        } finally {
            // If the thread fails to start, it starts from wokers Remove in w And decreasing wokerCount
            if (!workerStarted)
                // Decreasing wokerCount Will trigger tryTerminate Method
                addWorkerFailed(w);
        }
        return workerStarted;
    }
View Code

 

runWorker

When a task is added successfully, it actually executes the runWorker method, which is very important. In short, what it does is:

  • The first startup performs the initialization of the incoming task firstTask;
  • Tasks are then taken from the workQueue to execute, and if the queue is empty, wait so long for keepAliveTime.
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        // Worker Thread interrupts are suppressed in the constructor setState(-1),So here's what you need unlock This allows interruptions
        w.unlock();
        // Used to identify abnormal termination, finally in processWorkerExit Methods have different logic
        // by true Situation: 1.Executing the task threw an exception;2.Interrupted.
        boolean completedAbruptly = true;
        try {
            // If getTask Return null that getTask The Central Committee will workerCount Decrease, if this decrement is exceptional it will processWorkerExit Medium processing
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted. This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
                        && !wt.isInterrupted())
                    wt.interrupt();
                try {
                    // Some processing can be inserted before the task executes, and subclasses overload the method
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();// Perform user tasks
                    } catch (RuntimeException x) {
                        thrown = x;
                        throw x;
                    } catch (Error x) {
                        thrown = x;
                        throw x;
                    } catch (Throwable x) {
                        thrown = x;
                        throw new Error(x);
                    } finally {
                        // and beforeExecute Same, leave subclasses to overload
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }

            completedAbruptly = false;
        } finally {
            // End some thread cleanup
            processWorkerExit(w, completedAbruptly);
        }
    }
View Code

 

getTask

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        retry: for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            // 1.rs > SHUTDOWN therefore rs At least equal to STOP,The queued tasks are no longer processed at this time
            // 2.rs = SHUTDOWN therefore rs>=STOP Definitely not, you will also need to process the tasks in the queue unless the queue is empty
            // Both cases return null Give Way runWoker Sign out while Loop is the end of the current thread, so you must decrement
            // wokerCount
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                // Decreasing workerCount value
                decrementWorkerCount();
                return null;
            }

            // Mark if timeout is set when taking tasks from the queue
            boolean timed; // Are workers subject to culling?

            // 1.RUNING state
            // 2.SHUTDOWN Status, but there are tasks in the queue that need to be executed
            for (;;) {
                int wc = workerCountOf(c);

                // 1.core thread Allow timeout, then exceed corePoolSize Threads of must have timed out
                // 2.allowCoreThreadTimeOut == false && wc >
                // corePoolSize This is usually the case. core thread Even if idle, it will not be recycled, as long as it exceeds the thread
                timed = allowCoreThreadTimeOut || wc > corePoolSize;

                // from addWorker You can see the general wc Will not be greater than maximumPoolSize,So more concerned about the second half of the sentence:
                // 1. timedOut == false The first execution cycle, removing tasks from the queue is not null Method returns or
                // poll An exception occurred and the retry was attempted
                // 2.timeOut == true && timed ==
                // false:Look at the code behind workerQueue.poll Timeout timeOut Only then true,
                // also timed To be false,These two conditions are not valid at the same time (since there is a timeout then) timed Definitely true)
                // So the timeout will not continue executing but return null End thread.(Important: How do threads time out??)
                if (wc <= maximumPoolSize && !(timedOut && timed))
                    break;

                // workerCount Decrease, end current thread
                if (compareAndDecrementWorkerCount(c))
                    return null;
                c = ctl.get(); // Re-read ctl
                // The state of the thread pool needs to be rechecked because the thread pool may be SHUTDOWN
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }

            try {
                // 1.Tasks taken from the queue with a specified timeout
                // 2.core thread No timeout
                Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;// overtime
            } catch (InterruptedException retry) {
                timedOut = false;// Thread interrupted retry
            }
        }
    }
View Code

 

processWorkerExit

Thread exit does some cleanup in this way.

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        // If normal then runWorker Of getTask Method workerCount Has been subtracted
        if (completedAbruptly)
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // Cumulative threaded completedTasks
            completedTaskCount += w.completedTasks;
            // Remove threads with timeouts or exceptions from the thread pool
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        // Attempt to stop thread pool
        tryTerminate();

        int c = ctl.get();
        // runState by RUNNING or SHUTDOWN
        if (runStateLessThan(c, STOP)) {
            // Thread does not end abnormally
            if (!completedAbruptly) {
                // Thread pool minimum idle count, allowed core thread Timeout is 0, otherwise it is corePoolSize
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                // If min == 0 However, the queue is not empty to ensure that there is a thread to perform the tasks in the queue
                if (min == 0 && !workQueue.isEmpty())
                    min = 1;
                // Thread pool is not empty, so don't worry
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            // 1.Thread exited abnormally
            // 2.The thread pool is empty, but there are still tasks in the queue not executed. Look addWoker Methods for handling this situation
            addWorker(null, false);
        }
    }
View Code

 

tryTerminate

The processWorkerExit method attempts to call tryTerminate to terminate the thread pool.This method is executed after any action that may cause the thread pool to terminate: for example, reducing wokerCount or SHUTDOWN state to remove tasks from the queue.

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            // The following states are returned directly:
            // 1.Thread pool is still in RUNNING state
            // 2.SHUTDOWN Status but task queue is not empty
            // 3.runState >= TIDYING Thread pool has stopped or is stopping
            if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))
                return;

            // The following logic can only continue if the thread pool is terminated.
            // 1.SHUTDOWN Status, at which point new tasks are no longer accepted and the task queue is empty
            // 2.STOP State, when called shutdownNow Method

            // workerCount Thread pool cannot be stopped without 0,And then the thread is idle waiting
            // You need to interrupt the thread to wake it up to continue processing shutdown Signal.
            if (workerCountOf(c) != 0) { // Eligible to terminate
                // runWoker Method w.unlock So that it can be interrupted,getTask The method also handles interrupts.
                // ONLY_ONE:Only one thread needs to be interrupted to process shutdown The signal is OK.
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Get into TIDYING state
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        // Subclass overload: some resource cleanup
                        terminated();
                    } finally {
                        // TERMINATED state
                        ctl.set(ctlOf(TERMINATED, 0));
                        // Continue awaitTermination
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }
View Code

 

shutdown and shutdownNow

The shutdown method places the runState as SHUTDOWN and terminates all idle threads.

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // Thread pool state set to SHUTDOWN,Return directly if it is already at least this state
            advanceRunState(SHUTDOWN);
            // Notice here that all idle threads are interrupted: runWorker The waiting thread was interrupted → Get into processWorkerExit →
            // tryTerminate The method ensures that the remaining tasks in the queue are executed.
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

The shutdownNow method sets the runState to STOP.Unlike the shutdown method, this method terminates all threads.

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // STOP Status: New tasks are no longer accepted and tasks in the queue are no longer executed.
        advanceRunState(STOP);
        // Interrupt all threads
        interruptWorkers();
        // Return to a task that has not yet been executed in the queue.
        tasks = drainQueue();
    }
    finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

The main difference is that shutdown calls the interruptIdleWorkers method, while shutdownNow actually calls the interruptIfStarted method of the Worker class:

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            // w.tryLock A lock can be acquired indicating that the thread is not running because runWorker Execute tasks first in lock,
            // So the interrupt is guaranteed to be an idle thread.
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    }
    finally {
        mainLock.unlock();
    }
}
void interruptIfStarted() {
    Thread t;
    // At Initialization state == -1
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

This is the primary role of the Woker class mentioned earlier in implementing AQS.

Note: The shutdown method may be implicitly called in finalize.

This blog is basically code and comments, so it would look boring if you weren't analyzing the ThreadPoolExecutor source code.

 

Reprinted at: https://my.oschina.net/u/1052976/blog/550068

Posted by Jbert2 on Sat, 07 Sep 2019 16:36:03 -0700