在创建任务的时候提到过 worker.startTask() 函数:
/** * 添加任务,需要加锁 * @param runnable 任务 */ private void addWorker(Runnable runnable) { Worker worker = new Worker(runnable, true); worker.startTask(); workers.add(worker); }也就是在创建线程执行任务的时候会创建 Worker 对象,利用它的 startTask() 方法来执行任务。
所以先来看看 Worker 对象是长啥样的:
其实他本身也是一个线程,将接收到需要执行的任务存放到成员变量 task 处。
而其中最为关键的则是执行任务 worker.startTask() 这一步骤。
public void startTask() { thread.start(); }其实就是运行了 worker 线程自己,下面来看 run 方法。
第一步是将创建线程时传过来的任务执行(task.run),接着会一直不停的从队列里获取任务执行,直到获取不到新任务了。
任务执行完毕后将内置的计数器 -1 ,方便后面任务全部执行完毕进行通知。
worker 线程获取不到任务后退出,需要将自己从线程池中释放掉(workers.remove(this))。
从队列里获取任务其实 getTask 也是非常关键的一个方法,它封装了从队列中获取任务,同时对不需要保活的线程进行回收。
很明显,核心作用就是从队列里获取任务;但有两个地方需要注意:
当线程数超过核心线程数时,在获取任务的时候需要通过保活时间从队列里获取任务;一旦获取不到任务则队列肯定是空的,这样返回 null 之后在上文的 run() 中就会退出这个线程;从而达到了回收线程的目的,也就是我们之前演示的效果
这里需要加锁,加锁的原因是这里肯定会出现并发情况,不加锁会导致 workers.size() > miniSize 条件多次执行,从而导致线程被全部回收完毕。
关闭线程池最后来谈谈线程关闭的事;
还是以刚才那段测试代码为例,如果提交任务后我们没有关闭线程,会发现即便是任务执行完毕后程序也不会退出。
从刚才的源码里其实也很容易看出来,不退出的原因是 Worker 线程一定还会一直阻塞在 task = workQueue.take(); 处,即便是线程缩容了也不会小于核心线程数。
通过堆栈也能证明:
恰好剩下三个线程阻塞于此处。
而关闭线程通常又有以下两种:
立即关闭:执行关闭方法后不管现在线程池的运行状况,直接一刀切全部停掉,这样会导致任务丢失。
不接受新的任务,同时等待现有任务执行完毕后退出线程池。
立即关闭