线程池工作中,如果任务量很大,超过系统实际承载能力时,如果不予理睬,接着可能系统就崩溃了,所以jdk内置提供了线程池的4种拒绝策略,合理的解决这种问题。
线程池中线程已经用完不能再创建了,等待队列也排满了,此时如果再来新任务,就会执行下面的拒绝策略之一。
主要配合线程池使用
使用该策略会直接抛异常,阻止系统正常工作。
只要线程池没有关闭,该策略直接在调用者线程中,运行当前被丢弃的任务,虽然不会真的丢弃任务,但是任务提交线程的性能极有可能急剧下降。
该策略将丢弃即将执行的下一个任务,并尝试再次提交当前任务。
该策略默默丢弃没法处理的任务,不予处理。如果允许任务丢失,这可能是最好的一种解决方案了。
1)、先来看下 jdk内置拒绝策略的定义。
DiscardOldestPolicy 的源码实现:
作为子类,他们都实现了 RejectedExecutionHandler 接口 ,通过重写接口方法(参数 r 为任务线程,参数 e 为当前的线程池 ),以父类作为参数传入自定义的线程池 (如果觉得描述不清楚,可参看下面线程池通过接口回调的方式使用拒绝策略的小demo)。
不知道小伙伴们看着眼熟不,乍一看,呦呵,这个自定义线程池还默默的用了一下设计模式呢~
拒绝策略以接口形式作为参数,实际调用的时候传入的是该接口的子类,而线程池使用的是其重写的拒绝策略方法。soga~原来是使用了策略模式啊。但是如果您看这个线程池的核心工作代码,又能知道了,他还用了模板模式,所以准确来讲,他使用了典型的组合:模板模式+策略模式
2)、哦哦,原来酱紫啊,如果jdk内置拒绝策略无法满足你的需求,那你也可以自定义拒绝策略咯~
就是照着葫芦画片,也实现这个接口,并重写他的方法呀。调用的话,就按照上面的截图形式,把子类传进去,或者接口回调的形式也可以。
到此,拒绝策略介绍完毕。
如果觉得上面阐述的不够清楚,可以看下面的demo
public class RejectThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
MyTask task=new MyTask();
ExecutorService es= new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+" is discard");
}
});
for (int i=0;i<Integer.MAX_VALUE;i++){
es.submit(task);
Thread.sleep(10);
}
}
public static class MyTask implements Runnable{
@Override
public void run(){
System.out.println(System.currentTimeMillis()+": Thread ID :"+Thread.currentThread().getId());
try {
Thread.sleep(100);
}catch (InterruptedException e){
System.out.println(e);
}
}
}
}
结果;
该线程池核心线程数和最大线程数都是5,等待队列容量为10个,自定义拒绝策略,不抛出异常,避免任务提交端没做异常处理导致出现其他的状况。选择只打印丢弃任务的信息,比内置的DiscardPolicy策略高级一点点。
在实际应用中,可以将日志信息记录的更详细一点,来分析系统的负载和任务丢失的情况。
这种固定大小的线程池,最好不要设置无界队列,如果任务量很大,消费又很慢,那队列会一直扩容,很可能把内存撑爆。
参考书籍《java高并发程序设计》