线程池创建+拒绝策略
线程池
适合单个任务处理时间比较短
需要处理的任务数量很大
创建方式的选择:
线程池的创建方法有两种
- 使用Executors线程工具类 ,直接点 newXxxThreadPool (可以new四种)
- 一种是如下所示,手动创建线程池
线程池的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
关于使用哪一种方法创建,阿里开发手册中也提到了
第3条规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
第4条规定:线程池不允许使用Executors创建,而是通过ThreadPoolExecutor的方式创建,这样的处理方式能让编写代码的攻城狮更加明确线程池的运行规则,规避资源耗尽(OOM)的风险
所以最好使用第二种,采用手动创建线程池的方式
有什么区别和利害关系呢?
直接使用Executors工具类的话 可以创建四种线程,分别是
newFixedThreadPool() 线程数量固定的线程池
newCachedThreadPool() 可缓存线程的线程池
newScheduledThreadPool() 执行定时任务的线程池
newSingleThreadExecutor() 单线程线程池
有什么弊端呢?
先看看newFixedThreadPool()的构造函数
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
LinkedBlockingQueue 任务队列没有指定容量,说明可以添加大量的任务,如果大量任务堆积,可能会导致OOM
再看看newCachedThreadPool的构造函数
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
最大线程数设置成了Integer.MAX_VALUE,意味着可以创建大量线程,也可能导致OOM
所以综上所述,要使用手动创建线程池的方式
手动创建线程池:
线程池的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
构造方法好几个,下面这个是参数最多的
public ThreadPoolExecutor( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){
...
}
参数说明:
corePoolSize - 线程池核心线程的数量。 (长期维持的数量)
maximumPoolSize - 线程池的最大线程数。
keepAliveTime - 当线程数大于核心时,一旦超过此时间,会将多余的空闲线程杀掉
unit - keepAliveTime 的时间单位。
workQueue - 用来储存等待执行任务的队列。
threadFactory - 线程工厂。
handler - 拒绝策略。可以选择指定的,也可以自定义拒绝策略,实现接口即可
参数详细说明:
corePoolSize:
是线程池中长期保持的线程数量
maximumPoolSize:
线程池能够处理的最大线程数量,超过就要执行拒绝策略了(默认为报错)
keepAliveTime:
当线程池比较闲的时候,超过10s钟的时间可以把最大的线程数中不用的线程减掉,恢复成核心线程数的数量
(java中也有优化机制,当任务比较多的时候,会去新创建一些线程 执行任务;比较空闲的时候,会把新创建 的临时线程舍弃掉,最终会保持和核心线程数相等的线程数量)
执行流程:
当有任务的时候,先判断当前是否有空闲线程,有的话则执行
没有空闲流程的话,看是否可存入workQueue等待队列
队列没有满的话添加,加入队列中,排队等待执行
如果队列满了,再看是否超过了 最大的线程数,如果没超过 直接创建线程执行
如果超过了最大线程数,会执行拒绝策略
拒绝策略:
AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。 (默认)
DiscardPolicy:也是丢弃任务,但是不抛出异常。
DiscardOldestPolicy:忽略最早的任务(把最早添加任务到队列的任务忽略掉,然后执行当前的任务)
CallerRunsPolicy:把超出的任务交给当前线程执行
本来是线程池自己执行的,结果处理不过来就交给当前的主线程处理
演示几种拒绝策略
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, //核心线程数
4, //最大线程数
10, //空闲等待时间
TimeUnit.SECONDS, //空闲等待时间的单位
new LinkedBlockingQueue<>(3), //等待队列的长度
new ThreadPoolExecutor.AbortPolicy() //拒绝策略,抛异常
);
for (int i = 0; i < 8; i++) {
executor.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}
执行之后发现:
直接抛出异常了,没有没有抛出异常,说明可能是某个人物执行的比较快
最大线程数 + 队列中的数量 就是该线程池能承受的最大任务量 也就是 7
2.DiscardPolicy 是不会报出任何错的,只做忽略操作
3.DiscardOldestPolicy 执行忽略最早的,也是执行六次
4.CallerRunsPolicy 执行不了的交给主线程处理
main
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-4
自定义拒绝策略:
构造方法中,原本设置拒绝策略的参数,就不要设置了,在设置拒绝参数的位置
设置自定义的拒绝策略,可以使用匿名内部类 ,new RejectedExecutionHandler
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
4,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("自定义拒绝策略执行了...");
}
}
);
for (int i = 0; i < 8; i++) {
executor.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}
打印结果:
自定义拒绝策略执行了...
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-4
pool-1-thread-2
pool-1-thread-3