线程池复用原理

        线程池可以把线程和任务进行解耦,线程归线程,任务归任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。在线程池中,同一个线程可以从 BlockingQueue 中不断提取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中,不停地检查是否还有任务等待被执行,如果有则直接去执行这个任务,也就是调用任务的 run 方法,把 run 方法当作和普通方法一样的地位去调用,相当于把每个任务的 run() 方法串联了起来,所以线程数量并不增加。其中execute代码如下:

 1 public void execute(Runnable command) { 
 2     //如果传入的Runnable为空,就抛出异常
 3     if (command == null) 
 4         throw new NullPointerException();
 5     int c = ctl.get();
 6     /**
 7     * 当前线程数是否小于核心线程数,如果小于核心线程数就调用 addWorker() 
 8     * 方法增加一个 Worker,这里的 Worker 就可以理解为一个线程
 9     */
10     if (workerCountOf(c) < corePoolSize) { 
11         if (addWorker(command, true)) 
12             return;
13         c = ctl.get();
14     } 
15     if (isRunning(c) && workQueue.offer(command)) { 
16         int recheck = ctl.get();
17         if (! isRunning(recheck) && remove(command)) 
18             reject(command);
19         else if (workerCountOf(recheck) == 0) 
20             addWorker(null, false);
21     } 
22     else if (!addWorker(command, false)) 
23         reject(command);
24 }

 

 

我们先来分析上面部分代码,我们先分析下面这一段代码:

if (workerCountOf(c) < corePoolSize) {

    if (addWorker(command, true)) 
        return;
    c = ctl.get();
}

 

我们主要看addWorKer(comond,tue)方法,addWorker 方法的主要作用是在线程池中创建一个线程并执行第一个参数传入的任务,它的第二个参数是个布尔值,如果布尔值传入 true 代表增加线程时判断当前线程是否少于 corePoolSize,小于则增加新线程,大于等于则不增加;同理,如果传入 false 代表增加线程时判断当前线程是否少于 maxPoolSize,小于则增加新线程,大于等于则不增加,所以这里的布尔值的含义是以核心线程数为界限还是以最大线程数为界限进行是否新增线程的判断。addWorker() 方法如果返回 true 代表添加成功,如果返回 false 代表添加失败。

接下来我们看下面这部分代码

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

 

如果代码执行到这里,说明当前线程数大于或等于核心线程数或者 addWorker 失败了,那么就需要通过 if (isRunning(c) && workQueue.offer(command)) 检查线程池状态是否为 Running,如果线程池状态是 Running 就把任务放入任务队列中,也就是 workQueue.offer(command)。如果线程池已经不处于 Running 状态,说明线程池被关闭,那么就移除刚刚添加到任务队列中的任务,并执行拒绝策略。

接下来我们上面这部分代码的else分支逻辑:

else if (workerCountOf(recheck) == 0)
                addWorker(null, false); 

能进入这个 else 说明前面判断到线程池状态为 Running,那么当任务被添加进来之后就需要防止没有可执行线程的情况发生(比如之前的线程被回收了或意外终止了),所以此时如果检查当前线程数为 0,也就是 workerCountOf**(recheck) == 0,那就执行 addWorker() 方法新建线程。

接着我们再看最后一部分代码:

else if (!addWorker(command, false)) 
    reject(command);

 

执行到这里,说明线程池不是 Running 状态或线程数大于或等于核心线程数并且任务队列已经满了,所以此时需要添加新线程,直到线程数达到“最大线程数”,所以此时就会再次调用 addWorker 方法并将第二个参数传入 false,传入 false 代表增加线程时判断当前线程数是否少于 maxPoolSize,小于则增加新线程,大于等于则不增加,也就是以 maxPoolSize 为上限创建新的 worker;addWorker 方法如果返回 true 代表添加成功,如果返回 false 代表任务添加失败,说明当前线程数已经达到 maxPoolSize,然后执行拒绝策略 reject 方法。如果执行到这里线程池的状态不是 Running,那么 addWorker 会失败并返回 false,所以也会执行拒绝策略 reject 方法。

所以看到这里我们需要着重分析addWorker()方法,这里的 Worker 可以理解为是对 Thread 的包装,Worker 内部有一个 Thread 对象,它正是最终真正执行任务的线程,所以一个 Worker 就对应线程池中的一个线程,addWorker 就代表增加线程。我们看部分addWorker内的方法:

boolean workerStarted = false;
boolean workerAdded = false;
//worker是内部类实现了接口Runnable,封装了Thread
Worker w = null;
try {
    //获取队列第一个任务
    w = new Worker(firstTask);
    final Thread t = w.thread;
    if (t != null) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            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) {
        //调用线程的start方法。
            t.start();
            workerStarted = true;
        }
    }
} finally {
    if (! workerStarted)
        addWorkerFailed(w);
}

 

通过上图中的注释我们可以看出,addWork方法实际上是调用自己封装的线程的start方法来启动线程,我们继续看worker内部类的run方法是如何实现的:

public void run() {
    runWorker(this);
}

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 循环体中
        while (task != null || (task = getTask()) != null) {
            w.lock();
            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方法,而不是新建线程
                    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);
    }
}

 

从上图中我们可以看到内部类worker的run()方法实际上是调用runWorker(this)方法,实现线程复用的逻辑主要是在一个不同的循环体while中进行,所以在runWorker(this)方法中主要做了两件事:

通过取 Worker 的 firstTask 或者通过 getTask 方法从 workQueue 中获取待执行的任务。

直接调用 task 的 run 方法来执行具体的任务(而不是新建线程,调用线程的start()方法)。

 

posted @ 2022-11-24 10:19  weslie  阅读(72)  评论(0)    收藏  举报