Java Multithreading--2, ThreadPoolExecutor Detailed Explanation (2)

      The last article briefly introduced some of the more important constants of ThreadPoolExecutor. Next, we will mainly track the most important execute methods.

      On the source code:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    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);
}

      The code is very simple. One parameter is a variable of type Runnable. We have already introduced the principle of thread running. When the thread is finally executed, it will execute the run method of the Runnable object. Next, we will analyze the code step by step:

int c = ctl.get();

      Get the value of ctl. Since ctl is an atomic integer, the current value can be obtained through the get method. The first 3 bits identify the status, and the last 29 bits identify the number of threads.

if (workerCountOf(c) < corePoolSize) {

      The workerCountOf method, which we have introduced earlier, can calculate the number of threads in the last 29 bits of ctl. corePoolSize is the initial value in the ThreadPoolExecutor constructor, which identifies the running number of core threads. When it is determined that the current number of threads is less than the number of core threads, the function body is entered.

if (addWorker(command, true))

      Call the addWorker method, the command is the Runnable object passed by us, then let's trace the addWorker method, the source code:

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

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            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;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                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) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

      Suppose we call the execute method for the first time, then the current value of ctl is 1110 0000 0000 0000 0000 0000 0000, the first 3 bits are the Running state, and the last 29 bits are 0. Look at the first code of addWorker, 2 infinite loop bodies:

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

    // Check if queue empty only if necessary.
    if (rs >= SHUTDOWN &&
        ! (rs == SHUTDOWN &&
           firstTask == null &&
           ! workQueue.isEmpty()))
        return false;

    for (;;) {
        int wc = workerCountOf(c);
        if (wc >= CAPACITY ||
            wc >= (core ? corePoolSize : maximumPoolSize))
            return false;
        if (compareAndIncrementWorkerCount(c))
            break retry;
        c = ctl.get();  // Re-read ctl
        if (runStateOf(c) != rs)
            continue retry;
        // else CAS failed due to workerCount change; retry inner loop
    }
}

      Get the value of ctl, and then get the thread pool state (runState - rs, hereinafter referred to as rs) through ctl, the current rs value should be RUNNING, so the first judgment:

// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
    ! (rs == SHUTDOWN &&
       firstTask == null &&
       ! workQueue.isEmpty()))
    return false;

      In the case of normal operation, if there is no external call to the shutdown method or an exception occurs, the judgment will not pass, and the next piece of code will be run directly, which is also an infinite loop body:

int wc = workerCountOf(c);
if (wc >= CAPACITY ||
    wc >= (core ? corePoolSize : maximumPoolSize))
    return false;

      workCount - wc (hereinafter referred to as wc), the current number of active threads. In no special circumstances, the first judgment will not be true, because the capacity of CAPACITY is very large, the second judgment, when core is true, judge the number of active threads Whether it is greater than the number of core threads, if false, it is judged whether it is greater than the maximum number of pool boundary values. If the judgment is passed, the method body will be ended directly, and false will be returned. At this time, the number of wc should be 0.

if (compareAndIncrementWorkerCount(c))
    break retry;
c = ctl.get();  // Re-read ctl
if (runStateOf(c) != rs)
    continue retry;
// else CAS failed due to workerCount change; retry inner loop

      compareAndDecrementWorkerCount is an atomic operation on ctl:

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

   Compare whether the expected value is equal to the actual value. If it is equal, add one to the current value and return true. Note: Since the ThreadPoolExecutor object is in a multi-threaded environment, other threads may modify the value of ctl during this period, then the setting at this time will fail and a false value will be returned.

      If the setting is successful, it will directly jump out of the outermost loop. If it fails, re-acquire the value of ctl, and then re-determine whether rs has changed. If rs has changed, execute the next loop of the outer layer. If it has not changed, execute the current next cycle. one cycle.

      Assuming we set it successfully, the value of ctl will become: 1110 0000 0000 0000 0000 0000 0000 0001, check the next code:

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;

      Set some local variables, then start a try-catch method body:

try {
    w = new Worker(firstTask);
    final Thread t = w.thread;
    if (t != null) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // Recheck while holding lock.
            // Back out on ThreadFactory failure or if
            // shut down before lock acquired.
            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) {
            t.start();
            workerStarted = true;
        }
    }
} finally {
    if (! workerStarted)
        addWorkerFailed(w);
}

      Through the appeal code, we can see that a Worker object is created, and then a global variable thread of the worker is assigned to a temporary variable, and finally the start method of this temporary variable will be called, then the thread at this time will really start, Then the run method will also be called.

private final HashSet<Worker> workers = new HashSet<Worker>();
// ... some code
workers.add(w);

      workers is a global variable used to save a persistent storage of worker objects in memory. After the worker is saved successfully, the value of workerAdded will be set to true, and then the t.start method will be called to start the thread.

      In the following, we will analyze the code of Worker to analyze how the thread pool implements the function of the pool!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325569926&siteId=291194637
Recommended