1. 使用线程池的好处
线程池采用了池化技术,数据库连接池,HTTP连接池都利用了这个思想。
采用池化技术的好处在于:
- 降低频繁创建和销毁线程所带来的性能开销。
- 提高响应速度,无需等待资源创建。
- 资源统一创建和回收,便于维护和监控。
2. 线程池的创建
- 手动创建,配置线程池参数
- corePoolSize:线程池核心线程数量。默认情况下,线程池中线程的数量如果 <= corePoolSize,那么即使这些线程处于空闲状态,那也不会被销毁。
- maximumPoolSize:线程池中最多可容纳的线程数量。当一个新任务交给线程池,如果此时线程池中有空闲的线程,就会直接执行,如果没有空闲的线程且当前线程池的线程数量小于maximumPoolSize,就会创建新的线程来执行任务,否则就会将该任务加入到阻塞队列中,如果阻塞队列满了,就会创建一个新线程,从阻塞队列头部取出一个任务来执行,并将新任务加入到阻塞队列末尾。如果当前线程池中线程的数量等于maximumPoolSize,就不会创建新线程,就会去执行拒绝策略。
- keepAliveTime:当线程池中线程的数量大于corePoolSize,并且某个线程的空闲时间超过了keepAliveTime,那么这个线程就会被销毁。
- unit:就是keepAliveTime时间的单位。
- workQueue:工作队列。当没有空闲的线程执行新任务时,该任务就会被放入工作队列中,等待执行。
- threadFactory:线程工厂。可以用来给线程取名字等等
- handler:拒绝策略。当一个新任务交给线程池,如果此时线程池中有空闲的线程,就会直接执行,如果没有空闲的线程,就会将该任务加入到阻塞队列中,如果阻塞队列满了,就会创建一个新线程,从阻塞队列头部取出一个任务来执行,并将新任务加入到阻塞队列末尾。如果当前线程池中线程的数量等于maximumPoolSize,就不会创建新线程,就会去执行拒绝策略
2. 使用Executors工具类
是因为其可能使用无界队列或无限制线程数,容易导致内存溢出或系统崩溃,无法对资源进行有效控制。
3. 线程池工作流程图
4. 线程池的四种拒绝策略
线程池会在以下两种情况下拒绝提交任务。
- 调用shutdown等方法关闭线程池
- 线程池工作队列已满
四种拒绝策略:
- AbortPolicy:直接抛出异常
- DiscardPolicy:不做任何处理,静默拒绝提交的任务
- DiscardOldestPolicy:抛弃最老的任务,执行该任务
- CallerRunsPolicy:谁提交这个任务,谁负责执行这个任务。
这四种拒绝策略中只有CallerRunsPolicy不会有任务丢失的风险。不过CallerRunsPolicy也会有其他风险,如果提交的任务非常耗时,并且提交任务的线程是主线程,会造成阻塞,严重情况下可能导致OOM。
5. 如何设定线程池的大小
CPU密集型:corePoolSize = cpu核心数 + 1。本身会占据大量cpu,如果设置过多cpu,反而会造成不必要的切换。
IO密集型:corePoolSize = 2 * cpu核心数。IO操作很耗时,线程频繁阻塞,这时候需要增加线程数提高资源利用率。
6. 线程的关闭策略
shutdown():正在执行的任务会继续执行完,队列中未执行的任务继续等待执行;不会接收新任务
shutdownNow():尝试立即终止线程池,中断所有正在执行的线程,并移除队列中尚未执行的任务
7.线程池常用的阻塞队列
- LinkedBlockingQueue:链表结构,默认是无界队列
- SynchronousQueue:不存储任务,来一个任务就交给线程执行
- DelayedWorkQueue:内部采用的是“堆”的数据结构,按延迟的时间长短对任务进行排序,可以保证每次出队的任务都是当前队列中延迟时间最短的。
- PriorityBlockingQueue:带优先级的无界队列,任务需实现 Comparable 接口,适合需要按优先级执行任务的场景。