目录
5) 单线程线程池 SingleThreadExecutor
ThreadPoolExecutor
1) 线程池状态
- ThreadPoolExecutor 有五种状态,用一个 int 变量的高三位来保存线程池的状态,低 29 位来保存线程池中的线程数量
为什么用一个变量保存两个值??
将两个信息存储在一个原子变量 ctl 中,将线程池状态和线程数量合二为一,这样就可以用一次 CAS 操作同时对两个值进行赋值,保证了并发安全
- 线程池状态大小比较(从数字上来看):TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
2) 构造方法
3) 固定数量线程池 FixedThreadPool
- 核心线程数 == 最大线程数
- 没有超时时间:非核心线程不会被回收
- 使用无界队列:不会触发非核心线程的创建、有可能发生 OOM
- 适用场景:适用于任务量已知,相对耗时的任务
4) 带缓冲线程池 CacheThreadPool
- 核心线程数为 0,最大线程数 Integer.MAX_VALUE,所有线程都是非核心线程(救急线程),可以在这个范围内不停创建新的线程
- 超时时间是 60 秒,所有线程如果空闲了 60s 之后都会被回收
- 阻塞队列使用的是同步队列 SynchronousQueue:没有容量,如果没有线程来取任务就放不进去,放任务的线程会阻塞知道有非核心线程来取任务
- 非核心线程处理完任务之后,可以到同步队列中取任务
- 线程池表现:随着任务数量增大,线程数量不断增大,没有上限,当线程执行完任务并空闲 1min 后,线程就会被销毁
- 适用场景:任务密集且任务执行时间短
5) 单线程线程池 SingleThreadExecutor
- 核心线程数 == 最大线程数 ==1,只有 1 个核心线程来执行任务,任务执行完毕也不会释放
- 任务数量大于 1 时,会直接放入无界队列等待线程执行
- 适用场景:希望任务按顺序执行,按照队列中的顺序排队执行
- 和自己创建一个线程来执行任务的区别
- 自己创建的线程如果因为执行任务失败而终止,没有补救措施,任务队列中的任务也没有别的线程来执行了
- 单线程线程池中的线程如果因为执行任务失败而终止,线程池还会创建一个新的线程来执行任务,任务队列中的任务还是可以得到执行,线程池的正常工作可以得到保证
- 和 new FixedThreadPool(1) 的区别:
6) 提交任务
- void execute(Runnable r):执行任务,接收 Runnable,没有返回值
- submit:提交任务到线程池中,接收 Callable(Callable 有返回值,Runnable 没有返回值),用 future 获取返回值
7) 关闭线程池
1. void shutdown()
- 线程池状态变为 SHUTDOWN
- 不会接收新任务
- 不会中断正在执行的任务(已提交的任务会执行完=>正在执行的 + 已在队列中的)
- 不会阻塞调用 shutdown() 的线程(主线程)的执行
- 打断空闲线程
2. List<Runnable> shutdownNow()
- 线程池状态变为 STOP
- 不会接收新任务
- 用 interrupt 中断所有线程,包括正在执行任务的线程和空闲的线程
- 返回队列中尚未执行的任务 List<Runnable>
3. 其他方法
异步模式:Worker Thread 工作线程
- 定义:让有限的资源(工作线程)轮流异步处理无限多的任务
- 注意:不同类型的任务类型使用不同的线程池,可以 避免饥饿,提升效率
饥饿:不同于 第四章 共享模型——管程 (yuque.com)-线程活跃性中的饥饿,线程池中的饥饿是由于线程不足无法执行任务导致的
- 固定大小 的线程池才会有饥饿现象,带缓冲线程池(corePoolSize = 0, maxPoolSize = Integer.MAX_VALUE)不会
两个线程都去点餐了,点完餐会等其他线程去做菜,做完菜后才可以上菜,但是线程池中已经 没有多余的线程 去做菜了,导致这两个点餐的线程都在这死等做菜,导致饥饿现象,注意这不是死锁,因为不是相互等待
- 上面问题的原因:不同类型的任务都提交一个线程池当中,且任务之间有 依赖 关系
- 解决方法:不同的任务类型使用不同的线程池,各司其职