线程池ThreadPoolExecutor源码分析(二)

线程池中的工作线程是如何实现线程复用的?一个线程一般在执行完任务后就结束了,怎么再让他执行下一个任务呢? 

当我们往线程池添加任务的时候使用ThreadPollExcutor对象的execute(Runnable command)方法来完成的。那我们就来看一下这个逻辑部分的代码:

   public void execute(Runnable command) {
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            //1、当前工作线程小于corePoolSize,新建worker线程并返回
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
       
        //2、如果当前工作线程大于等于corePoolSize,添加至任务到队列。并进行二次确认(确认队列是否关闭,进行回滚)
        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);
        }
       
        //3、添加队列失败后,则尝试新建非core线程,失败则拒绝任务。
        else if (!addWorker(command, false))
            reject(command);
    }

可以看出:ThreadPoolExecutor.execute()的功能就是:

  • 将任务添加至阻塞队列workQueue:workQueue.offer(command)
  • 根据corePoolSize和maxPool,选择是否创建Worker:addWorker()

其中我们可以看到核心逻辑是执行addWorker,线程复用的实现应该在worker中,打开addWorker()方法观察:

private boolean addWorker(Runnable firstTask, boolean core) {
  w = new Worker(firstTask);
  final Thread t = w.thread;
  ...//代码
  t.start();
  ...//代码
}

上面代码逻辑中新建Worker对象,其实可以猜测到就是新建线程并启动。到Worker类看一下,是一个内部私有类,实现了Runnable接口。在run方面里面只有一句runWorker:

private final class Worker    extends AbstractQueuedSynchronizer    implements Runnable{.....
    
     public void run() {
            runWorker(this);
    }

                                                                                }

参考了网上一个简化的runWorker(this)方法的写法:

    final void runWorker(Worker w) {
    Runnable task = w.firstTask;
    w.firstTask = null;
    while (task != null || (task = getTask()) != null) {
            try {
                task.run();
            } finally {
                task = null;
            }
        }
}

很显然这个runWorker很不得了,里面一个大大的while循环,当我们的task不为空的时候它就永远在循环,并且会源源不断的从getTask()获取新的任务,继续看getTask()方法:

//很显然这个方法是从队列中获取任务workQueue
private Runnable getTask() {
       ...//代码
       // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        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;
        }
    }
}

分析到这里线程复用的流程就算完了,最核心的一点是:新建一个Worker内部类就会建一个线程,并且会把这个内部类本身传进去当作任务去执行,这个内部类的run方法里实现了一个while循环,当任务队列没有任务时结束这个循环,则这个线程就结束。

总结:线程重用的核心是,我们知道,Thread.start()只能调用一次,一旦这个调用结束,则该线程就到了stop状态,不能再次调用start。则要达到复用的目的,则必须从Runnable接口的run()方法上入手,可以这样设计这个Runnable.run()方法(就叫外面的run()方法):
它本质上是个无限循环,跑的过程中不断检查我们是否有新加入的子Runnable对象(就叫内部的runnable:run()吧,它就是用来实现我们自己的任务),有就调一下我们的run(),其实就一个大run()把其它小run()#1,run()#2,...给串联起来了,不停地处理我们提交的Runnable任务,基本原理就这么简单。


期间参考众多资料,具体有哪些自己也理不清了。如果冒犯,敬请联系~

posted @ 2020-01-20 11:09  Simon-Lau  阅读(165)  评论(0编辑  收藏  举报