这是我参与更文挑战的第8天,活动详情查看: 更文挑战
关于线程池了解多少
线程池是一个池化思想的线程管理工具。使用线程池,降低了资源的消耗,通过池化技术重复利用已经创建的线程。提高了相应速度,因为省去了线程的创建就可以执行。提高了线程的管理,可以进行线程的统一分配调优和监控。提供了更强大的拓展功能,比如延期执行和定期执行。
总体设计
线程池的运行主要分为两个部分,一个是线程的管理,一个是任务的管理,任务管理充当生产者的角色,线程管理充当消费者的角色。线程池是通过ctl这个原子变量去维护生命周期和线程池中线程数量的。
线程池的状态
线程池一共有五个状态
- Running:可以接受任务
- ShutDown:关闭不能接受新提交的任务,但是可以处理阻塞队列里的任务
- Stop:不能接受新任务也不能处理阻塞队列里的任务,正在处理任务的线程也会中断
- Tidying:所有任务都已经终止,workCount有效线程数量为0
- Terminated:执行Terminated()方法后进入该状态。
阻塞队列
| 名称 | 描述 |
|---|---|
| ArrayBlockingQueue | 用数组实现的有界队列。默认先进先出,支持公平锁和非公平锁 |
| LinkedBlockingQueue | 由链表结构组成的有界队列。默认先进先去,最大值是Integer.MAX_VALUE |
| PriorityBlockingQueue | 支持按照线程优先级排序的无界队列。默认先进先出,也可以实现排序进行,同等优先级的不支持再排序。 |
| DelayQueue | 实现PriorityBlockingQueue来实现延迟无界队列。添加任务的时候,支持设置延迟多久才可以获取该任务。 |
| SynchronousQueue | 一个不储存元素的队列。每一个put操作必须等待一个take操作,否则不能添加任务。 |
| LinkedTransferQueue | 由链表构成的无界队列。相对于其他队列,多了transfer和retransfer |
| LinkedBlockingDeque | 链表构成的双向阻塞队列。列表头部和尾部都可以添加和移除元素,多线程并发时,可以将竞争降低一半。 |
拒绝策略
任务拒绝是为了保护线程池。线程池有个最大的容量,当缓冲队列也满了之后,线程池中的线程到达maxinumPoolSize的时候,就需要拒绝该任务。采取任务拒绝策略,保护线程池。
拒绝策略是一个接口,通过实现接口可以实现拒绝策略的自定义,或者使用JDK提供的4种拒绝模式。
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
四种JDK的拒绝模式:
| 名称 | 策略 |
|---|---|
| AbortPolicy | 丢弃任务,会抛出一个RejectedExecutionException异常,这是默认的拒绝策略,在任务不能提交的时候抛出异常,及时反馈程序的运行状态。 |
| CallerRunsPolicy | 由调用线程去执行该任务。这种情况需要让所有线程都执行完毕。 |
| DiscardOldestPolicy | 丢弃任务队列最前面的任务。然后重新提交被拒绝的任务。 |
| DiscardPolicy | 丢弃任务,但是不抛出异常。 |
Worker线程管理
线程池尾了掌握线程的状态并且维护线程的生命周期。设计了线程池内的工作线程Worker。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{ //Worker持有的线程 final Thread thread; //初始化的任务,可以为null Runnable firstTask; } Worker这个工作线程实现了Runnable,并且持有一个线程thread。一个初始化的任务FirstTask。thread在初始化的时候通过调用ThreadFactory来创建的。
线程的回收 线程池需要管理线程的生命周期。需要在线程长时间不运行的时候进行回收。线程池通过使用一张Hash表去持有线程的引用。这样可以通过添加、移除引用来控制线程的生命周期。
Worker是通过继承AQS来实现独占锁这个功能的。没有使用ReentrantLock可重入锁,就是冲着AQS的不可重入的特性来反应线程的状态。lock方法没有获取到独占锁。则表示当前线程正在执行任务。如果正在执行任务,也不应该中断线程。如果lock获取到了独占锁,说明线程现在处在空闲状态,没有处理任务,此时对线程进行中断。最后线程池在执行shutdown或者tryTerminate方法时候,会调用interruptIdeWorkers方法来中断空闲线程,interruptIdeWorkers中的TryLock方法会判断线程池中的线程是否是空闲状态,如果是,则回收线程。
线程池中的线程销毁依赖的是JVM自动的回收,线程池要做的就根据当前线程池状态来维护一定数量的线程的引用,防止这部分线程被JVM回收。当线程决定哪些线程需要回收时,只需要将其引用取消掉。Worker创建出来就会不断轮询,然后获取任务去执行,核心线程可以无限等待获取任务。非核心线程要限时获取任务。当Worker无法获取到任务,也就是任务为空的时候。循环会结束,Worker会主动消除自身在线程池内的引用。
try {
while (task != null || (task = getTask()) != null) {
//执行任务
}
} finally {
processWorkerExit(w, completedAbruptly);//获取不到任务时,主动回收自己
}
线程回收的工作是由processWorkerExit完成的。
Worker线程执行任务
在Worker类中的run方法调用了runWorker来执行任务。while循环不断的通过getTask()来获取任务,getTask()从阻塞队列中获取任务。如果线程池正在停止,则要保证线程是中断状态,否则要保证线程不是中断状态。然后执行任务,如果getTask()获取到的结果为Null,则跳出循环,执行processWorkerExit(),销毁线程。
关于线程池最重要的核心线程数最大线程数。
快速响应用户请求的线程池,比如快速查看商品信息的,这里的目的是为了让用户快速的得到想要的商品信息。此时不应该设置队列去缓冲并发任务。调高corePoolSize和maxinumPoolSize去快去执行任务。