Understanding threadpoolexecutor source code (2) Clever design and reading of the Execute function

Source: Internet
Author: User

Threadpoolexecutor.exe cute () Source Code provides a lot of comments to explain the design considerations of this method. The following source code is from jdk1.6.0 _ 37.

public void execute(Runnable command) {        if (command == null)            throw new NullPointerException();        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {            if (runState == RUNNING && workQueue.offer(command)) {                if (runState != RUNNING || poolSize == 0)                    ensureQueuedTaskHandled(command);            }            else if (!addIfUnderMaximumPoolSize(command))                reject(command); // is shutdown or saturated        }    }

The use of so many if-else is for performance consideration, to reduce the scope of use of the lock, to avoid holding the mainlock lock throughout the execute method. We can see that only the three methods addifundercorepoolsize, ensurequeuedtaskhandled, and addifundermaximumpoolsize need to be locked. If the newly submitted task does not enter these three methods, you do not need to hold the lock. Let's take a look at whether the design of the execute method can effectively reduce the number of times you enter these three methods to achieve fast forward and fast output.


4th lines of code: poolsize> = corepoolsize. Why should I add this judgment?

If all core threads are started in advance through prestartallcorethreads, or the submitted task has set the current poolsize to the core size corepoolsize, and the core thread will not die (allowcorethreadtimeout = false does not allow the core thread to exit after timeout, And the thread exits because the task execution process does not throw an exception), the number of threads in the thread pool will not be smaller than corepoolsize, if a new task is submitted later, the addifundercorepoolsize method will not be entered.Poolsize> = corepoolsize is used to reduce the number of times the addifundercorepoolsize function is enabled, and reduce the number of times the lock is obtained and released.. If poolsize <corepoolsize, it will enter addifundercorepoolsize. This method will determine the status and current size of the thread pool when locking, and then decide whether to add a new thread to process the task. Because addifundercorepoolsize holds the mainlock, it can prevent other threads from modifying the thread pool concurrently. That is to say, we can ensure that no new thread needs to be added. The call to addifundercorepoolsize rechecks runstate and pool size under lock (they change only under lock) So prevents false alarms that wocould add threads when it shouldn't. This is the comment in the source code.


5th lines of code if (runstate = running & workqueue. offer (command), if the program can go to this line of code, from the perspective of task submitter, the thread pool size has reached the core size (although this is not the case, because there is no lock, concurrent modifications may exist, and threads in the thread pool may also die. If conditions are met:
Runstate = running & workqueue. Offer (command) This means that the thread pool is still running and the task queue is successful. Why can't the execution of the program still end here, or the code that follows?Because no lock is applied, the judgment conditions are not necessarily reliable.. Consider the following two scenarios: if the task is calling offer at the beginning (it has not been successfully inserted into the blocking Queue), All threads in the thread pool will die, there is no thread to process the currently submitted task. If the task has just been successfully queued and another thread has called shutdownnow () to close the thread pool, follow the shutdownnow function syntax, this task should not be processed. Due to these two special cases, subsequent processing is required. But may also fail to add them when they shocould. This is compensated within the following steps. That is to say:Addifundercorepoolsize can ensure that no new thread is required, but it cannot be ensured that the new thread is added when the new thread needs to be added.. Therefore, when a task is queued up successfully, you need to determine whether to delete the task or whether to add a thread to process the task under the protection of the lock.


6th lines of code if (runstate! = Running | poolsize = 0). If the task is successfully queued (workqueue. after offer () returns true), if the thread pool is closed or there are no surviving threads, You need to execute ensurequeuedtaskhandled (command). In this case, the task may not be properly disposed. If the thread pool is still in the running state after the task is successfully queued and there are surviving threads, the newly submitted task will be processed. Why? We know that to disable threadpoolexecutor, there are only three ways: Call the shutdown method, call the shutdownnow method, and the last worker in the thread pool exits (corresponding to the workerdone method ). We know from the source code that the three methods that may cause thread pool shutdown will eventually call the tryterminate () method. This method is required if the thread pool is to be terminated.

/**     * Transitions to TERMINATED state if either (SHUTDOWN and pool     * and queue empty) or (STOP and pool empty), otherwise unless     * stopped, ensuring that there is at least one live thread to     * handle queued tasks.     *     * This method is called from the three places in which     * termination can occur: in workerDone on exit of the last thread     * after pool has been shut down, or directly within calls to     * shutdown or shutdownNow, if there are no live threads.     */    private void tryTerminate() {        if (poolSize == 0) {            int state = runState;            if (state < STOP && !workQueue.isEmpty()) {                state = RUNNING; // disable termination check below                Thread t = addThread(null);                if (t != null)                    t.start();            }            if (state == STOP || state == SHUTDOWN) {                runState = TERMINATED;                termination.signalAll();                terminated();            }        }    }

When the number of thread pools in the thread pool is 0, if the thread pool is in the running or shutdown status and the task queue is not empty, a new thread is added to process the task; if the thread pool is in the status, or is in the shutdown status and the task queue is empty, the thread pool will exit. That is to say, if the thread pool has not been terminated after the task is successfully queued, the task will be reasonably disposed.

If the thread pool is not in runstate before or during Task insertion! = Running or no surviving thread poolsize = 0, you need to consider how to handle the submitted task, which is completed by ensurequeuedtaskhandled. If the IF (runstate = running & workqueue. Offer (command) condition is true, then if (runstate! = Running | poolsize = 0) rarely appears,In most scenarios, you do not need to execute the ensurequeuedtaskhandled method, so you do not need to obtain or release the lock.. Next, let's look at the source code to see how the ensurequeuedtaskhandled method handles exception scenarios.

/**     * Rechecks state after queuing a task. Called from execute when     * pool state has been observed to change after queuing a task. If     * the task was queued concurrently with a call to shutdownNow,     * and is still present in the queue, this task must be removed     * and rejected to preserve shutdownNow guarantees.  Otherwise,     * this method ensures (unless addThread fails) that there is at     * least one live thread to handle this task     * @param command the task     */    private void ensureQueuedTaskHandled(Runnable command) {        final ReentrantLock mainLock = this.mainLock;        mainLock.lock();        boolean reject = false;        Thread t = null;        try {            int state = runState;            if (state != RUNNING && workQueue.remove(command))                reject = true;            else if (state < STOP &&                     poolSize < Math.max(corePoolSize, 1) &&                     !workQueue.isEmpty())                t = addThread(null);        } finally {            mainLock.unlock();        }        if (reject)            reject(command);        else if (t != null)            t.start();    }

This method holds the mainlock, so it can prevent concurrent modifications to the thread pool. If the thread pool is not in the running status (State! = Running), and the newly submitted tasks also reside in the task queue (workqueue. remove (command) returns true), the current submitted task is denied (call the reject (comma) method ). That is to say,As long as the thread pool is not in the running status, it will definitely refuse to execute the currently submitted task, unless the task has been processed by the thread in the thread pool(Workqueue. Remove returns false ). It is not distinguished whether it is a thread pool closed by Shutdown () or a thread pool closed by shutdownnow. If a new task is submitted and the execution process enters the ensurequeuedtaskhandled () function, the task may be rejected or executed normally. If the thread pool is in the running or shutdown status (State <Stop), and there are no surviving threads in the thread pool, and the task queue is not empty, a new thread needs to be added, to process the tasks waiting for execution.


We can see that through multiple lockless condition judgments, the competition for the mainlock lock can be effectively reduced when the task is submitted, and the concurrent execution can also be ensured, the newly submitted tasks and tasks waiting for execution in the thread pool can be properly processed. I don't know how Master Doug Lea came up with such a clever design and implementation of the execute () method! Although the execute method can improve the performance, it sacrifices the readability and simplicity of the Code. For concurrent programs, make it right before you make it faster.

 

Understanding threadpoolexecutor source code (2) Clever design and reading of the Execute function

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.