这部分内容是跟着前一篇的锁的,无奈超字数了,只能拆分成两篇了~
线程池
线程池的核心就是:队列 + 线程复用机制
线程池主要解决两个问题:
1:通过减少任务间的调度开销 (主要是通过线程池中的线程被重复使用的方式),来提高大量任务时的执行性能;
2:提供了一种方式来管理线程和消费,维护基本数据统计等工作,比如统计已完成的任务数;
线程池跟线程一样,也是有状态的。
线程池的状态扭转:
Q :线程池是如何实现任务调度的?
任务调度流程:
Q :线程池如何实现复用?
线程池复用的实现在于理解Worker这个类设计。 Worker本身实现了Runable接口,并且,在初始化时就启动了一条线程用执行任务:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
好了,思考,如果是你,你会如何设计?我们知道,线程池还有一个阻塞队列,所以,是否可以这样实现:不断地从阻塞队列中取出任务来执行,这样不就实现了复用线程了吗??
实际上,线程池也是这样实现的:
runWorker方法就是执行任务,采用while循环不断从阻塞队列中去除任务来执行。
逻辑如图:
这里,又引出另外一个问题了,如果是不断轮询,那么如何回收非核心线程的呢?
这里可以发散一下思维的,我们知道,很多第三方库都有复用的概念的,举个例子,OkHttp的TCP连接池,复用的是TCP连接;还有我们Glide,它复用的是Bitmap的内存空间。OkHttp是通过Deque + cleanRunnable来管理复用与清理的,而Glide则是通过LruCache来实现的。
Q :如何实现超过60s就被回收?
Worker被创建出来后,就会不断地进行轮询,然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当Worker无法获取到任务,也就是获取的任务为空时,循环会结束,Worker会主动消除自身在线程池内的引用。
try {
while (task != null || (task = getTask()) != null) {
//执行任务
}
} finally {
processWorkerExit(w, completedAbruptly);//获取不到任务时,主动回收自己
}
getTask()方法实现:
private Runnable getTask() {
...
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
}
线程回收的工作是在processWorkerExit方法完成的,如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程
Q: 线程池的种类?
Q:线程池的拒绝策略
RejectedExecutionHandler提供了四种方式来处理任务拒绝策略:
- 直接丢弃(DiscardPolicy)
- 丢弃队列中最老的任务(DiscardOldestPolicy)。
- 抛异常(AbortPolicy)
- 将任务分给调用线程来执行(CallerRunsPolicy)。