Thread pool source code analysis (creation, reuse, timeout processing)

After jdk1.5, the official implementation of the thread pool ThreadPoolExecutor for us, through this thread pool, we can one-time (not one-time, but slowly add threads until the thread pool is full) pre-execute expensive threads Allocated, and all allocated threads are reusable. Next, we will analyze the creation process of the following thread pool and the thread allocation problem of the thread pool according to Executors.newFixedThreadPool as the entry point.

When we execute Executors.newFixedThreadPool, we keep following up on the inside of this method, and we can find that it actually calls the constructor of ThreadPoolExecutor:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
ThreadPoolExecutor is a thread pool object that is responsible for building the appropriate context to execute Runnable objects, and is responsible for managing the life cycle of all threads it creates. In this constructor, we only care about two parameters. The number of core threads of the thread pool represented by nThreads (there is also a maximum number of threads, which is consistent with the number of core threads), and the LinkedBlockingQueue blocking queue object is used to manage all tasks and submit tasks to worker threads (only in the current When the thread pool is full, a task is put into the blocking queue).

After the constructor is executed, we will get a ThreadPoolExecutor object. Note that the object will not start any threads at this point. There is an important property in this object, which are as follows:

1.private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
This field represents the control state of the thread pool. This field has two conceptual attributes: 1.workercount (representing the number of valid threads); 2.runState (representing the working state of the thread pool, running, shutting down etc).
2.private final BlockingQueue<Runnable> workQueue;
Used to store tasks and submit tasks to worker threads (the above blocking queue parameter injection location)

3.private final HashSet<Worker> workers = new HashSet<Worker>();
Represents a collection of all work tasks. Worker is a class that inherits Runnable. It has two properties: final Thread thread and Runnable firstTask. When a thread is to be started, the internally created thread is obtained through worker.get thread (because when the thread object is created, the worker Pass itself to thread through this as a parameter, so after opening the thread, the run method of the worker is still running, and inside the run method is actually the firsttask object of the current worker object called)

Let's take a look at the specific details of submitting a task to ThreadPoolExecutor:

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);
    }
From this code, we can see that when adding a Runnable task, there are four cases:

        1. The task object is null and a null pointer exception is thrown

        2. If the number of current worker threads is less than the number of core threads, then directly call addWorker (details are described below), create a new thread to perform the task, and add the class Worker where the thread is located to the thread pool, if successful, return directly

        3. If the number of current worker threads is greater than or equal to the number of core threads, and the thread pool is still running, the task will be successfully added to the blocking queue. After success, a second check is performed to see if the thread pool is available. If it is not available, then Remove the task from the blocking queue. If the blocking queue removal task is successful at this time, the handler is called for the currently rejected task. If the thread pool is unavailable but the blocking queue removal fails or the thread pool is available, then if the effective number of threads in the thread pool is 0, the addworker method is executed and an empty task is passed.

         4. If the number of current worker threads is greater than or equal to the number of core threads + the size of the blocking queue (the default is unbounded), then directly call the addworker method to create a new thread to execute the task, if the Boolean parameter passed to addWorker is true, then Description If the current number of worker threads must be less than the maximum thread pool size (maximumPoolSize), if it is false, it must be less than the maximum thread size (corePoolSize), and if the return value is false, the task request is rejected directly.

After reading the above four situations, the focus now is on the specific implementation of addworker (Runnable task, boolean core). Let's look at the implementation of this code:

private boolean addWorker(Runnable firstTask, boolean core) {
        ............//Omitted code, mainly some verification

        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;
    }

The code block omitted in the first paragraph is mainly used to judge and check whether the current thread pool is available, whether the task is null, whether the number of valid threads exceeds the number of core threads or the maximum number of threads (varies according to different cores) and so on. Here we mainly analyze the second functional code. This code first defines two variables of type boolean to determine whether the Worker (see above) task is enabled, and whether the created Worker object is added to the thread pool (essentially a hashset collection). Then pass the task to the Worker constructor The worker creates the Worker object and obtains the Thread object that holds the Worker object. At this time, if the status of the thread pool is available or the status of the thread pool is closed but the task is null, the obtained thread object will be judged, because the Worker has not been added to the thread pool at this time, if the thread is in In the alive state (the start method is called), the thread state exception error is thrown, otherwise, the previously created Worker object is added to the thread pool. At this point the workerAdded property is true. Start executing the t.start method, the thread starts to start, and workerStarted is set to true at this time. If these codes are executed, and workerStarted is still false due to throwing an exception and being finally caught , the thread pool workers remove the added worker object and reduce the current number of effective threads by CAS operation.

The above only explains the creation of threads in the following situations and dynamically adding threads to the thread pool. To recap, the role of the thread pool is to help us avoid manually creating and opening threads, and to control the total number of threads, but it is also important to reuse previously created threads to avoid frequent creation of new threads to increase consumption . The embodiment of this function is mainly in the run method of the Worker class:

/** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
    }
Let's look at the runWoker method:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
  **note:       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 {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        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 {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

The first thing here is to obtain the firstTask object of the current Worker object (we first construct the task task object that implements the Runnable interface passed in by the Worker object). After obtaining it, the firstTask reference of the Worker object can be pointed to null. Let's look at the line of code I marked with note. This is a while loop. It will first determine whether the current task is null. If not, execute the following code directly. The execution order is: beforeExecute -> task.run() -> afterExecute; If the current task is null, then it will call the getTask() method to get the task object from the blocking queue (1. The third case mentioned earlier is added; 2. For the detailed implementation details of getTask, please see Line 1039 of the ThreadPoolExecutor class), if the task obtained at this time is still empty, call processWorkerExit(w, completedAbruptly) to remove the current thread from the thread pool. Since this is a loop of code, as long as there are tasks in the blocking queue, the Worker will continue to take out tasks for execution without creating new threads, which achieves the purpose of thread reuse.

Now there is one last question, if there are no currently executable tasks, how to deal with these idle threads (timeout processing) ? There are two cases here. If the total number of current threads does not exceed corePoolSize, the threads in the current thread pool will never be recycled by default, unless the allowCoreThreadTimeOut property is set to true; if the total number of current threads exceeds corePoolSize and the current blocking queue is empty, there is no way to Execute the task, then execute the poll(keepAliveTime, TimeUnit.NANOSECONDS) operation at this time, because the blocking queue is empty at this time, then the time of keepAliveTime must be blocked, the timeout is set to true, and the loop continues. At this time, it will enter the one I marked with note. Code block, at this time, the CAS operation will be used to reduce the total number of threads currently executing, and return null, and the processWorkerExit(w, completedAbruptly) method of the outer method will recycle the thread. The main implementation of this code is as follows:

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

        for (;;) {
            ......
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //wc represents the number of all Worker instances in the thread pool
  note:     if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

So far, creating a thread pool, dynamically adding threads to the thread pool, thread multiplexing, and handling of timeout threads have all been introduced . When there is time later, I will introduce the status of different thread pools in java and the differences between various thread pools based on experience and books.




Guess you like

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