Source code analysis of concurrent programming implementation principle of thread pool

foreword

In the previous article, we introduced the use of thread pools, so now we have a question: how is the thread pool implemented? After all, curiosity is human nature. Then let's take a look today, open his source code, and find out.

1. Start with Demo

The above picture is the simplest demo. Let's start from this demo to see the source code, first step by step.

First, we manually created the thread pool, used a limited number of blocking queues, used the default thread factory provided by the thread pool factory, and a default rejection policy. Let's see how the default thread factory is created?

The default thread factory obtains the thread group from the current thread, sets the default thread name prefix pool-xxx-thread-xxx, and is forced to be a non-daemon thread with the default priority.

Then we look at the constructor of ThreadPoolExecutor:

Nothing special, mostly some judgment.

Ok, so let's see how the execute method is implemented.

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    // c = -536870911
    int c = ctl.get();
    //  工作线程数量小于核心线程池设定数,则创建线程。
    if (workerCountOf(c) < corePoolSize) {
        // 如果添加成功则直接返回
        if (addWorker(command, true))
            return;
        // 否则再次获取活动线程数量
        c = ctl.get();
    }
    // 如果线程池正在运行,并且添加进队列成功
    if (isRunning(c) && workQueue.offer(command)) {
        // 再次对线程池状态检查, 因为上面 addWorker 过了并且失败了,所以需要检查
        int recheck = ctl.get();
        // 如果状态不是运行状态,且从队列删除该任务成功并尝试停止线程池
        if (! isRunning(recheck) && remove(command))
            // 拒绝任务
            reject(command);
        // 如果当前工作线程数量为0(线程池已关闭),则添加一个 null 到队列中
        else if (workerCountOf(recheck) == 0)
            // 添加个空的任务
            addWorker(null, false);
    }
    // 如果添加队列失败,则创建一个任务线程,如果失败,则拒绝
    else if (!addWorker(command, false))
        // 拒绝
        reject(command);
    }
}

First, empty judgment.

Then judge, if the working thread is less than the set core thread, create a thread and return, if the number of working threads is greater than or equal to the number of core threads, try to put the task into the queue, if it fails, try to create a task of maximumPoolSize . Note that in the remove method, the method has attempted to stop the thread pool from running.

From this code, you can see that the most important methods are addWorker and workQueue.offer(command) this code, one is to create a thread, the other is to put it into the queue. The latter is to add tasks to the blocking queue.

So let's take a look at the addWorker method.

2. addWorker method-----create thread pool

private boolean addWorker(Runnable firstTask, boolean core)

This method is very long. The landlord will talk about the two parameters of this method. The first parameter is Runnable type, which represents the first task of a thread in the thread pool. If the second parameter is true, the core core thread is created. , if false, create a maximumPoolSize thread. The life cycles of the two threads are different.

The landlord intercepts the final code in this method:

Among them, in this method, a Worker object is created, which represents the task object. Let's take a look at the constructor of this class:

Create a thread through the thread factory. Note that this is passed. Therefore, in the above code, calling the start method of the thread attribute of the worker object is actually calling the run method of the class. So how is the run method of the modified class implemented?

Called its own runWorker method. This method is very important.

3. Worker.runWorker(Worker w) method ------- the core method of thread pool


    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            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);
        }
    }

First, let's talk about the main logic of the method:

  1. First execute the run method of firstTask.
  2. The loop then gets the tasks in the blocking queue and calls their run method.
  3. If the task in the thread pool is abnormal, throw an exception and stop running the thread pool.

This method can be said to be the core of the thread pool. The number of core tasks set at the beginning is to directly call the start method to start the thread. After starting, the thread is not closed, but has been waiting on the blocking queue. It is important that the task executes the task's run method, not the start method.

There are several points to note in this method that the thread pool is left for us to extend. Before executing the task, the beforeExecute method will be executed. This method is empty by default. We can implement this method. After the task execution ends, in the finally block There is an afterExecute method, which is also empty, and we can extend it.

After seeing the code here, the landlord was amazed, Doug Lea can be said to be a god-like figure.

So, there is another method in the thread pool, how is submit implemented? In fact, the core logic is also the runWorker method, otherwise the landlord would not say that this method is the core of the thread pool.

Then let's see how the submit method is implemented.

4. The implementation principle of the submit method.

This method also takes the execute method in the end, so the logic is basically the same, what is the difference? Let's see. We see that the second line of code creates a RunnableFuture object. RunnableFuture is an interface. What is the specific implementation? Let's see:

FutureTask

A FutureTask object, which is also a thread object:

Then we look at the run method of this method.

The core logic of this method has been framed, the call method is called, a return value is returned, and in the set method, the return value is set in a variable, and if it is an exception, the exception is set in a variable. Let's look at the set method:

This method changes the task state from new to COMPLETING through CAS, and then sets the outcome variable, which is the return value. Finally, call the finishCompletion method to complete some variable cleanup.

So what if you get the return value from submit? It depends on the get method:

This method will judge the status. If the status has not been completed, call the awaitDone method to wait. If completed, call the report to return the result.

I saw the outcome variable that was just set. If the status is normal, it will return directly. If the status is canceled, an exception will be thrown, and the rest will also throw an exception.

Let's go back to the awaitDone method and see how the method waits.

This method has an infinite loop until a certain status is returned. If the status is greater than COMPLETING, that is, it is successful, it will return to the status. If it is in progress, it will give up the CPU time slice to wait. If neither, let the thread block waiting. Where to wake up? The thread is woken up in the finishCompletion method.

This method loops the linked list of waiting threads and wakes up each thread in the linked list.

Another point to note is that the done method will be executed after the task is executed. The JDK is empty by default, and we can extend this method. For example, Spring's concurrent package org.springframework.util.concurrent has two classes that override this method.

5. Summary

Well, here, we know the basic implementation principle of the thread pool, and it has also solved the landlord's doubts. It can be said that the core method of the thread pool is the runWorker method with the blocking queue. When the thread is started, it will be removed from the queue. Take out the task in the queue and execute the run method of the task. It can be said that the design is very clever. The callback thread callback is also through this method, JDK encapsulates the FutureTask class to execute their call method.

good luck!!!!

Guess you like

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