目录
线程的创建与销毁都是需要消耗资源的,为了减少线程的创建与销毁的开销以及产生过多线程带来的过分调度,我们通常使用线程池来管理线程。
线程池的创建有两种方式,一个是使用ThreadPoolExecutor类,另一个是使用Executors类。
一、 ThreadPoolExecutor
1.构造函数:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
参数说明:
- corePoolSize:核心线程数的最大值
- maximumPoolSize:线程池最大线程数
- keepAliveTime:空闲线程最大存活时间,如果线程数大于corePoolSize,则多出来的线程会在没有任务后指定时间关闭。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用
- unit:参数keepAliveTime的时间单位
- workQueue:用于缓存任务的阻塞队列
- threadFactory:线程工厂,主要用来创建线程
- handler:表示当拒绝处理任务时的策略
其中corePoolSize、maximumPoolSize、workQueue之间的关系,我们通过向线程池添加新的任务来说明。
(1)如果没有空闲的线程执行该任务且当前运行的线程数少于corePoolSize,则添加新的线程执行该任务。
(2)如果没有空闲的线程执行该任务且当前的线程数等于corePoolSize同时阻塞队列未满,则将任务入队列,而不添加新的线程。
(3)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于maximumPoolSize,则创建新的线程执行任务。
(4)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数等于maximumPoolSize,则根据构造函数中的handler指定的策略来拒绝新的任务。
2.handler拒绝策略
当 workQueue
已满,且池中的线程数等于 maximumPoolSize
时,线程池拒绝添加新任务时所采取的策略。
为啥需要拒绝策略?
线程已经全部都在工作了,而且可以缓冲的队列也满了,如果继续不停地往里添加任务,可能会超出服务器的负荷,把服务器打挂,骆驼还有被多加的一根稻草压垮的时候呢。
拒绝策略有4种:
策略 | 策略内容 |
ThreadPoolExecutor.AbortPolicy() | 抛出RejectedExecutionException异常 |
ThreadPoolExecutor.CallerRunsPolicy() | 由向线程池提交任务的线程来执行该任务 |
ThreadPoolExecutor.DiscardPolicy() | 抛弃当前的任务 |
ThreadPoolExecutor.DiscardOldestPolicy() | 抛弃最旧的任务(最先提交而没有得到执行的任务) |
其中后3种 CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy 拒绝策略不是一个好的处理方式。
CallerRunsPolicy
在非线程池以外直接调用任务的run方法,可能会造成线程安全上的问题;DiscardPolicy
默默的忽略掉被拒绝任务,也没有输出日志或者提示,开发人员不会知道线程池的处理过程出现了错误;DiscardOldestPolicy
中e.getQueue().poll()的方式好像是科学的,但是如果等待队列容量已满,那么就会不断有任务被抛弃,差不多竹篮打水了!
最科学的的还是 AbortPolicy 提供的处理方式:抛出异常,由开发人员进行处理,因此它被设置为defaultHandler。
3.线程池的状态
线程池具有以下五种状态。当创建一个线程池时初始化状态为 RUNNING 。
线程池 的状态 | 说明 |
---|---|
RUNNING | 允许提交并处理任务 |
SHUTDOWN | 不允许提交新的任务,但是会处理完已提交的任务 |
STOP | 不允许提交新的任务,也不会处理阻塞队列中未执行的任务, 并设置正在执行的线程的中断标志位 |
TIDYING | 所有任务执行完毕,池中工作的线程数为0,等待执行terminated()钩子方法 |
TERMINATED | terminated()钩子方法执行完毕 |
调用线程池的 shutdown 方法,将线程池由 RUNNING(运行状态)转换为 SHUTDOWN状态。
调用线程池的 shutdownNow 方法,将线程池由 RUNNING 或 SHUTDOWN 状态转换为 STOP 状态。
SHUTDOWN 状态 和 STOP 状态 先会转变为 TIDYING 状态,最终都会变为 TERMINATED。
二、Executors
生成线程池方法 | 作用 |
---|---|
CachedThreadPool | 必要时创建新线程,空闲线程保持60s |
FixedThreadPool | 线程数固定的线程池 |
SingleThreadExecutor | 只有单个线程的执行器 |
ScheduledThreadPool schedule(Callable<T> task, long time, TimeUnit unit) schedule(Runnable task, long time, TimeUnit unit) | 定时线程
预定在time时长之后开始执行 |
SingleThreadScheduledExecutor | 单个线程的定时执行器 |
1.马甲
Executors的创建线程池方法看似参数简化了不少,其实它底层实现只是封装了ThreadPoolExecutor加了默认参数而已!也就是说,它就是个披了马甲的ThreadPoolExecutor !
2. Executors里埋的坑
简化参数不是你的错,但是带来坑就不对了!
我们看下这个 newFixedThreadPool, 它的缓冲队列是new了一个 LinkedBlockingQueue<Runnable>,这会造成什么后果?
这样缓冲队列的最大值就是 Integer.MAX_VALUE,可能会堆积大量的任务请求,导致OOM。
SingleThreadExecutor 的队列也是这样,也会有同样的问题。
另外, CachedThreadPool 允许创建的线程也是Integer.MAX_VALUE,不断向其中添加任务可能会创建过多的线程耗尽系统资源或者产生OOM。
因此,《阿里巴巴Java开发手册》的编程规范中禁止了开发中使用Executors创建线程池,一律改用ThreadPoolExecutor类。