【Java线程】线程池的拒绝策略、异常捕获

使用线程池之前,先思考下面几个问题:

  1. 该异步场景适合用线程还是线程池?
  2. 使用JAVA默认的线程池,还是自定义的线程池?
  3. 使用JAVA默认的拒绝策略,还是自定义的拒绝策略?当Java提供的拒绝策略无法满足我们的业务场景时,需要我们自定义拒绝策略。涉及到自定义拒绝策略的场景的业务就比较复杂了,一般来说肯定是不想抛弃该任务而做的补偿措施。
  4. 是否需要捕获拒绝策略异常?一般捕获的目的是为了打印日志,方便后期做些追踪、调查。

针对第二点,我们需要慎重再慎重。你需要思考,你的业务场景对任务是否执行到底有多敏感。以「金融项目」为例,每次的任务都涉及到金钱,当任务队列满的时候,新的任务肯定是不能抛弃的啊。新任务放不到任务队列时,这时候我们应该采取补偿措施,比如把新任务放到一个「阻塞队列」里面,然后新任务等待被执行。

拒绝策略

1、拒绝策略使用场景

Q: 什么时候需要使用拒绝策略呢?
A: 当任务数量超过系统实际承载能力的时候就要用到拒绝策略了,可以说它是系统超负荷运行的补救措施。简言之,就是线程用完,队列已满,无法为新任务服务,则需一套机制来合理的处理这些问题。

2、JDK内置拒绝策略

JDK 提供了四种内置拒绝策略,我们要理解并记住,有如下的四种:
1、DiscardPolicy: 默默丢弃无法处理的任务,不予任何处理
2、DiscardOldestPolicy: 丢弃队列中最老的任务, 尝试再次提交当前任务
3、AbortPolicy(默认): 直接抛异常,阻止系统正常工作。
4、CallerRunsPolicy: 将任务分给调用线程来执行,运行当前被丢弃的任务,这样做不会真的丢弃任务,但是提交的线程性能有可能急剧下降。

3、自定义线程池创建方式

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
       ...
    }

以下是我们通常的使用方式(自定义线程池):

方式1:
线程池创建之后,指定拒绝策略. 如果不知道则使用默认的拒绝策略AbortPolicy。

int corePoolSize = 1;
int maximumPoolSize = 1;
BlockingQueue queue = new  ArrayBlockingQueue<Runnable>(1);
ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize,  maximumPoolSize,0, TimeUnit.SECONDS, queue ) ;
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy ());
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

方式2:
创建线程池的时候指定拒绝策略

 private static final ThreadPoolExecutor NEW_MESSAGE_COUNT_POOL = new ThreadPoolExecutor(
            8, 20, 5, TimeUnit.MINUTES,
            new ArrayBlockingQueue<>(16), new ThreadPoolExecutor.DiscardPolicy()
    );

方式3:
把线程池当做一个Compent注入到Spring容器中使用。

@Configuration
public class ExecutorConfiguration {


    @Bean
    public ThreadPoolExecutor taskAuditSavePoolExecutor(){
        return new ThreadPoolExecutor(2, 5, 1L,
                TimeUnit.MINUTES, new ArrayBlockingQueue<>(10),new ThreadPoolExecutor.DiscardPolicy());
    }

    @Bean
    public ThreadPoolExecutor emailPullAllExecutor(MessagePullAllExecutorConfig config){
          return new ThreadPoolExecutor(
                  config.getCorePoolSize(),
                  config.getMaximumPoolSize(),
                  config.getKeepAliveTime(),
                  TimeUnit.SECONDS,
                  new ArrayBlockingQueue<>(config.getWorkQueueCapacity(),
                  new ThreadPoolExecutor.DiscardPolicy())
          );
    }
  }

4、自定义拒绝策略

1.简单示例

通过实现RejectedExecutionHandler接口,自定义一个拒绝策略类,重写它的rejectedExecution()方法:
public class CustomRejectionHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println(r.toString() + "被拒绝了,执行入库操作,之后手动补偿");
    }
}

测试下:

public class SpringbootApplication {
    
    private static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) throws Exception {
        CustomRejectionHandler customRejectionHandler = new CustomRejectionHandler();
        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), customRejectionHandler);
        for (int i = 0; i < 20; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("第" + atomicInteger.getAndIncrement() + "个任务被执行");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

结果:
在这里插入图片描述

2.JAVA线程池自定义拒绝策略以及利用反射获取任务参数

https://blog.csdn.net/baidu_17353319/article/details/121532270

这个链接好好看,里面说明了不同的拒绝策略底层代码的实现逻辑。

线程池异常处理

1、run函数中的try catch是必须的

runnable里面的 run 函数里面的逻辑必须 try…catch

//如果用的是submit,如果这里不加try catch 会导致异常被”吃掉“ 
PULL_TASK_EXECUTOR.execute(new Runnable() {
 
            @Override public void run() {
                try{
                    // ...业务任务。
                }catch (Exception e){
                    
                }
            }
        });

2、其他异常捕获

1.通过设置UncaughtExceptionHandler打印异常日志

该方式我没有模拟过。

1、

       //如果是线程模式
       Thread t = new Thread();
       t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
 
           public void uncaughtException(Thread t, Throwable e) {
              LOGGER.error(t + " throws exception: " + e);
           }
        });
		//如果是线程池的模式:
        ExecutorService threadPool = Executors.newFixedThreadPool(1, r -> {
            Thread t = new Thread(r);
            t.setUncaughtExceptionHandler(
                (t1, e) -> LOGGER.error(t1 + " throws exception: " + e));
            return t;
        });

2、自定义线程池如何捕获线程异常

2.双层try catch嵌套(项目中的使用)

我在项目中是通过双层try catch使用的:

@Slf4j
@Service
public class DemoServiceImpl implements DemoService {

    private ThreadPoolExecutor taskAuditSavePoolExecutor = new ThreadPoolExecutor(2, 5, 1L,
            TimeUnit.MINUTES, new ArrayBlockingQueue<>(10));

    @Override
    public void testThreadPool(String param) {
    // 第一层:捕获线程池抛出的异常。 典型的是:拒绝策略抛出的异常。RejectedExecutionException是最顶层的拒绝策略异常接口。
        try {
            taskAuditSavePoolExecutor.execute(() -> {
             // 第二层:捕获任务内部发生的异常。
                try {
                    log.info("param:{}", param);
                    Thread.sleep(10000);
                } catch (Exception e) {
                    log.error(
                            "error, param:{}", param
                    );
                }
            });
        }catch (RejectedExecutionException rejectedExecutionException) {
            log.info("exception,start execute rejection policy,param:{}", param);
        }
    }
}
@SpringBootTest
class EmailDemoApplicationTests {

    @Autowired
    DemoService demoService;

    @Test
    void contextLoads() {
        for (int i = 0; i < 20; i++){
            demoService.testThreadPool(i + "");
        }
    }
}

结果:
在这里插入图片描述

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@来杯咖啡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值