线程池
线程池的优点
- 通过复用线程减少创建和销毁线程的资源消耗
- 不用重新创建线程,提高响应速度
- 方便管理,线程是系统稀缺资源,如果无限制创建会消耗系统资源,降低稳定性。使用线程池可以进行统一分配、调优和监控
线程池创建
1 | public ThreadPoolExecutor(int corePoolSize, |
- corePoolSize: 线程池核心线程数
- maximumPoolSize:线程池最大线程数大小
- keepAliveTime:线程池中非核心线程空闲的存活时间
- unit:线程空闲存活时间单位
- workQueue:存放任务的阻塞队列
- threadFactory:创建线程的工厂,可以给创建的线程设置有意义的名字,便于排查问题
- handler:线程池的饱和策略
线程池组成
线程池由工作线程、任务队列组成,其中工作线程 Worker 继承AQS,维护在一个HashSet 中任务队列是 BlockingQueue,防止在多线程环境下出现并发问题。
线程池执行
- 提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。
- 如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
- 当线程池里面存活的线程数已经等于corePoolSize了, 并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。
- 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。
拒绝策略
- AbortPolicy(抛出一个异常,默认的)
- DiscardPolicy(直接丢弃任务)
- DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
- CallerRunsPolicy(交给线程池调用所在的线程进行处理)
异常处理
- Runnable 或 Callable 中使用 try/catch 捕获并处理异常
- submit 提交任务, Feature.get() 处理异常
- Executors实例化时传入自己的ThreadFactory设置Thread.setUncaughtExceptionHandler处理异常
- 重写 ThreadPoolExecutor.afterExecute() 方法, 处理传递的 Throwable.
工作队列
- ArrayBlockingQueue(有界队列): 用一个数组实现的有界阻塞队列, FIFO 排序
- LinkedBlockingQueue(可设置容量的队列):基于链表的阻塞队列, FIFO 排序,容量可设置,否则是无边界队列,最大长度 Integer.MAX_VALUE,Executors.newFixedThreadPool线程池使用了这个队列。
- DelayQueue:延迟队列,任务定时周期的延迟执行队列,根据定时的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列
- PriorityBlockingQueue:优先级队列,具有优先级的无界阻塞队列
- SynchronousQueue:同步队列,不存储元素的阻塞队列,每个插入操作必须等另一个线程调用移除操作,否则插入操作一直处于阻塞状态,newCachedThreadPool使用了这个队列
线程池状态
RUNNING
该状态的线程池会接受新任务,并处理阻塞队列中的任务
调用线程池的 shutdown()方法,可以切换到 SHUTDOWN 状态
调用线程池的 shutdownNow() 方法,可以切换到 STOP 状态
SHUTDOWN
该状态的线程池不会接受新任务,但会处理阻塞队列中的任务
队列为空,并且线程池中执行的任务也为空,进入 TIDYING 状态
STOP
该状态线程不会接收新任务,也不会处理阻塞队列中的任务,并且会中断正在运行的任务
返回阻塞队列中正在执行的任务,进入 TIDYING 状态
TIDYING
该状态表明所有的任务已经运行终止,记录的任务数量为 0
terminated()执行完毕,进入 TERMINATED 状态
TERMINATED
该状态表示线程池彻底终止
线程池配置
对于计算密集型任务,一个有 N 个处理器的系统通过使用 N+1 个线程的线程池来获得最优的利用率(计算密集型任务巧合在某时刻因为发生一个页错误或因为其他原因暂停,刚好有一个额外的线程,可以确保这种情况下 CPU 周期不会中断工作)。
对于包含了 I/O 和其他阻塞操作的任务,不是所有的线程都会在所有的时间被调度,为了正确设置线程池长度,需要估算任务花在等待的时间与用来计算的时间的比率;最优池大小为:CPU 数目 * 目标 CPU 使用率 * (1 + 等待时间/计算时间)