线程池
一、为什么使用线程池?
降低消耗,线程可以重复使用,降低创建和销毁的销毁
提高响应速度,不需要等到线程创建了再执行,多线程可以并发执行
线程池可以加强线程的监控和管理,进行统一分配。
二、线程池的核心属性有哪些
- corePoolSize(核心线程数):当有任务来时,会创建线程,即使存在空闲线程,也会创建新线程。
- workQueue(队列):当核心线程数满时,新来的任务会加入到队列中。
- maximumPoolSize(最大线程数):线程池允许达到的最大线程数,当队列满时,判断任务是否达到最大线程数,未达到则创建线程,满了则执行拒绝策略。
- keepAliveTime(保持存活时间):当线程数量超过核心线程数,任务执行完后,多余的线程不会马上销毁,会等keepAliveTime后再销毁。
- threadFactory(线程工厂):创建线程的工厂
- handler(拒绝策略):当线程池状态不是Running或者已经达到最大线程数,并且队列已经满的时候会触发拒绝策略
- 线程池运行流程:
三、线程池的拒绝策略有哪些?
- AbortPolicy(中止策略):中断任务,抛出异常
- DiscardPolicy(抛弃策略):抛弃任务,直接丢弃任务
- CallerRunsPolicy(调用者运行策略):不拒绝任务,在调用者的线程上执行任务。如果任务不可丢弃使用该策略
- DiscardOldPolicy(抛弃最老策略):抛弃阻塞队列中最老未执行的任务,也就是下一个要执行的任务。如果任务存在优先级,不要使用该策略
四、线程池的状态
- RUNNING:接收并处理排队任务
- SHUTDOWN:停止接收任务,会继续处理队列的任务
- STOP:不接收任务,也不会处理队列的任务,会中断正在执行的任务
- TIDYING:所有任务都已终止,workerCount 为零,线程转换到 TIDYING 状态将运行 terminated() 钩子方法。
- TERMINATED:terminated() 已完成
五、常见的阻塞队列
- ArrayBlockingQueue:有界限的数组队列,按照先进先出处理任务
- LinkedBlockingQueue:有界限/无界限的链表队列,按照先进先出处理任务,效率高于ArrayBlockingQueue,
Executors.newFixedThreadPool、Executors.newSingleThreadExecutor 使用了该队列 - PriorityBlockingQueue:具有优先级的无界限队列,按照优先级排序,排序按照自然排序或者Comparator 来定义的
- SynchronousQueue:不是一个真正的队列,而是一种交互机制,他会把任务接收放入SynchronousQueue,必须有线程等待接收该任务。如果没有线程等待,则判断线程是否小于最大线程数,大于则执行拒绝策略,小于则创建线程。Executors.newCachedThreadPool使用了该队列
- DelayedWorkQueue:延迟的工作队列,无界队列。Executors.newScheduledThreadPool
六、线程池的ctl,以及这样设计的好处?
- ctl是一个AtomicIntegr原子整数,包含了workCount(线程的有效数量-低29位)和runState(线程池的工作状态-高3位)
- 好处在于,操作workCount和runState属性时必须是同一时刻,原子操作,如果分为两个属性,那么势必要加锁,而使用ctl原子整数封装两个属性则避免了开销。
七、为什么使用ThreadPoolExecutor创建线程池?
- Executors.newCachedThreadPool创建线程池,默认的最大线程数为Integer.MAX_VALUE,当任务激增,会不断创建线程,导致内存溢出
- Executors.newFixedThreadPool、Executors.newSingleThreadExecutor创建线程时使用了LinkedBlockingQueue队列,该队列大小为为Integer.MAX_VALUE,当任务激增超过核心线程数时会将新的任务放入队列,队列中任务会越来越多,导致内存溢出
八、线程池的大小配置多少合适?
- IO密集型:我们的操作离不开IO,常见的IO如,网络IO(RPC调用),磁盘IO(数据库操作),往往IO的等待时间会占很大一部分时间,这个时候,我们一般开启更多的线程让CPU更充分的使用。
线程数=CPU数*2(网上说法)
线程数=CPU数*CPU利用率(任务等待时间/任务执行时间 +1)
- CPU密集型:线程数=CPU数+1