背景
在我们的日常开发中肯定会遇到线程池的使用,那么随着jdk8的使用发现,future.get()这个API的使用也很普及了,当然重点不是这个api而是我们在设置线程池参数的时候如果使用自带的四种拒绝策略,那没什么问题,因为人家都给你处理好了,那如果想自定义拒绝策略呢?那么这就是本篇博文说的重点,那就是自定义拒绝策略时需要注意那么坑
创建一个线程池
我们来模拟下这个问题:首先创建如下的线程池配置,核心线程数是1,最大线程数也是1,阻塞队列的长度也是1,那也就是说如果我们有5个任务进来,一个任务会立刻处理,一个会在队列中等待,其他三个会走拒绝策略逻辑
public class ThreadLocalTest {static ThreadPoolExecutor productThreadPoolExecutor = new ThreadPoolExecutor(corePoolSize: 1,maximumPoolSize: 1,keepAliveTime: 1TimeUnit.SECONDS,new LinkedBlockingQueue<>( capacity: 1),new MyThreadFactory(threadStartName:"product"),MyRejectedPolicy()
)
自定义拒绝策略
这个自定义拒绝策略里面其实就打印个日志没有抛出异常,也就是说不管你处理什么逻辑,但是如果不抛出异常的话呢,那么这个任务其实是没有结束的
public static class MyRejectedPolicy implements RejectedExecutionHanddier{@overridepublic void rejectedExecution(Runnable r, ThreadPopolExecutor e)if(e.getThreadFactory() instanceof MyThreadFactory){MyThreadFactory myThreadFactory = (MyThreadFactorye.getThreaif("product".equals(myThreadFactory.getThreadFactoryNameSystem.out.println("线程池有任务被拒绝了,请关注");))
)
模拟
我们模拟五个任务,然后去触发我们的自定义拒绝策略,并且会返回一个结果,把这些结果否放在一个list集合里面,然后不断去get到结果,然后观察下是否会阻塞主线程呢,答案其实是回一直阻塞的,随着任务量变大会导致00M,具体大家可以本地做个模拟
public static void main(String[] args) throws Exception(while (true){TimeUnit.SECONDS.sleep(timeout:1);new Thread(() ->{ArrayList<Future<Integer>> futurelist = new ArrayLisst<>()int productNum = 5;for(inti=0;i<productNum;i++){try{int finalI=i;Future<Integer> future = productThreadPoolExecutor.submit(() ->{System.out.println("Thread.currentThread().getName() = " + Thred)return final * 10});futureList.add(future)} catch (Exception e){e.printStackTrace();}}
}for (Future<Integer>future:futurelist){try{Integer integer=future get();System.out.println(integer);System.out.println("future.get()= " + integer);} catch (Exception e){e.printStackTrace();}).start();
分析原因
其实导致上面的原因其实也不难,如果大家看过线程池源码的网友就很好理解了
首先我看下submit方法
我们会把每个task都会包装成一个futureTask任务,然后我们会执行execute方法,然后返回这个futureTask
那导致OOM原因是因为这个futureTask实例不断飙升,没有释放,继续
future get()方法内部
这个方法内部有这样一个判断逻辑,如果s小于等于这个状态值也就是1的时候,会执行awaiDone方法,这个方法就是阻塞逻辑
总结
上面两个方法就是核心了,也就是说在刚开始把task任务包装成FutureTask任务的时候,也就是new FutureTask()初始化的状态值是0,所以调用get方法的时候状态值还是0,并没有把状态值改变成1,所以对于线程池而已这个任务并没有完成,就会一直阻塞,那如何处理呢?有两种方式
1、首先我们可以在自定义拒绝策略里面可以抛出异常,因为如果抛出异常了之后会在execute方法里面走到自定义拒绝策略,这个方法一个是run方法的入口一个就是拒绝策略的入口,然后在业务代码外部有个try。。catch捕获到,就不会走到把任务放到集合这行代码
2、还有一种方式就是在get的时候加个超时时间
for (Future<Integer>future:futurelist){try{Integer integer = future.get(timeout: 10,TimeUnit.SECONDS);System.out.println(integer);System.out.println("future.get() = : "+ integer);} catch (Exception e){e.printStackTrace();}
}
3、顺便说一点为什么默认的拒绝策略没有这些问题呢,是因为默认的拒绝策略也会抛出异常的,人家帮你处理好了,所以如果你自定义的话就需要按照这样的逻辑去抛出异常