Explore ThreadPool Executor workflow in-depth source code

Posted by latinofever on Sat, 11 May 2019 21:48:38 +0200

Summary

This article mainly analyses the workflow of thread pool at the code level. If you want to know the workflow of thread pool directly, you can see the previous article. Java thread pool

Happy journey to enjoy source code has begun

First, the overall execution process

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
        //If the number of threads in the work is less than the core thread, execute directly
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //Core thread is full, join blocking queue
        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);
        }
        //Attempt to add tasks to non-core threads and refuse to execute if the join fails
        else if (!addWorker(command, false))
            reject(command);
    }

Sum up

  1. If the number of core threads is not full, create new core threads directly to perform tasks. If there are idle threads, reuse idle threads
  2. The number of core threads is full, and tasks to be performed are added to the blocked queue
  3. Core threads are full, blocking queues are full, non-core threads are created to perform tasks (non-sequential associated processes, non-core threads will be recycled if they are idle and reach survival time), and if there are idle threads, they can be reused.
  4. When the core thread is full, the blocking queue is full, and the non-core thread is full, the task will be rejected and the Rejected Execution Handler will be called.

Next, let's look at how to create threads, as long as it's an addWorker() implementation

    private boolean addWorker(Runnable firstTask, boolean core) {
    //The code has been tailored just to make it more concise
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        //Create core threads or non-core threads, controlled by core variables.
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                if (workerAdded) {
                //Thread up
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

Sum up
The above thing is a new worker object and calls start. If you are familiar with the relationship between Thread and runnable, guess that t is Thread and Wroker is the second encapsulation of runnable.

Next, let's decrypt Worker

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        Worker(Runnable firstTask) {
        //Thread state, which maintains the thread state. This article is not about thread state, so we can ignore it here. We can talk about it more carefully in the future.
            setState(-1);
            this.firstTask = firstTask;
            //Construct a thread
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker. */
        public void run() {
        	//Called by thread.start() method and executed to run()
            runWorker(this);
        }
}

Next, take a look at the runWorker approach

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
       
        boolean completedAbruptly = true;
        try {
        //Remove tasks from the task queue and execute them.
            while (task != null || (task = getTask()) != null) {
                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 {
        //End process
            processWorkerExit(w, completedAbruptly);
        }
    }

What this method does is to constantly take tasks out of task to execute, so the problem arises. According to the analysis above, the thread is terminated (a thread exits as long as the execution of run ends). So how does the core thread survive? In fact, all the puzzles are in the getTask method.

    private Runnable getTask() {
        boolean timedOut = false; 
            try {
            //Judge whether it is a core thread, non-core thread poll, core thread take, that is, how many s timeouts do non-core threads, core threads save.
            //The polling method is how long will the queue be waiting if it is null. If there are new tasks in the queue, the polling method will return to the new task immediately after the queue is null. That is to say, it will exit normally.
            //take() method is that if the queue is null, it is blocked until a task is added to the queue. So this implements the maintenance of core threads.
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
    }

So the core thread's survival is achieved by blocking the queue, which is the secret of poll and take. So if there is no task in the thread pool, the thread is blocked and consumes cpu resources. So the core thread should not be set too large in general. The general cpu core number + 1 is enough. Of course, if it's a small app, it's better to set it smaller.

summary

So there is so much thread pool analysis, I hope it will be helpful to you all.

Topics: Java less