使用线程池之前,先思考下面几个问题:
针对第二点,我们需要慎重再慎重。你需要思考,你的业务场景对任务是否执行到底有多敏感。以「金融项目」为例,每次的任务都涉及到金钱,当任务队列满的时候,新的任务肯定是不能抛弃的啊。新任务放不到任务队列时,这时候我们应该采取补偿措施,比如把新任务放到一个「阻塞队列」里面,然后新任务等待被执行。
Q: 什么时候需要使用拒绝策略呢?
A: 当任务数量超过系统实际承载能力的时候就要用到拒绝策略了,可以说它是系统超负荷运行的补救措施。简言之,就是线程用完,队列已满,无法为新任务服务,则需一套机制来合理的处理这些问题。
JDK 提供了四种内置拒绝策略,我们要理解并记住,有如下的四种:
1、DiscardPolicy: 默默丢弃无法处理的任务,不予任何处理
2、DiscardOldestPolicy: 丢弃队列中最老的任务, 尝试再次提交当前任务
3、AbortPolicy(默认): 直接抛异常,阻止系统正常工作。
4、CallerRunsPolicy: 将任务分给调用线程来执行,运行当前被丢弃的任务,这样做不会真的丢弃任务,但是提交的线程性能有可能急剧下降。
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())
);
}
}
通过实现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();
}
}
});
}
}
}
https://blog.csdn.net/baidu_17353319/article/details/121532270
这个链接好好看,里面说明了不同的拒绝策略底层代码的实现逻辑。
runnable里面的 run 函数里面的逻辑必须 try…catch
//如果用的是submit,如果这里不加try catch 会导致异常被”吃掉“
PULL_TASK_EXECUTOR.execute(new Runnable() {
@Override public void run() {
try{
// ...业务任务。
}catch (Exception e){
}
}
});
该方式我没有模拟过。
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、自定义线程池如何捕获线程异常
我在项目中是通过双层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 + "");
}
}
}
结果: