系统开发过程中遇到了线程池的使用,这篇文章主要记录一下线程池使用过程中遇到的问题和思考。
- 自定义线程池
-
自定义线程池
对于如何自定义线程池以及参数设置,请移步溪源《“打工人”初识线程池及自定义线程池实战》 -
注入Spring容器中
为什么要放入Spring容器中呢???思考如果不放入容器中,会存在什么问题。—资源浪费。如果不放入容器中,每次执行任务时都会创建线程池,执行完任务再关闭线程池。如果任务请求次数很多,便会创建很多线程池,岂不是造成很大的资源浪费,故将其放入容器中管理。
示例:
@PostConstruct
@Bean(name = "myThreadPool")
private ThreadPoolExecutor createThreadPool() {
return new ThreadPoolExecutor(5, 10,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));
}
- 线程工厂
其实实现线程工厂接口,也是为了定制化自己的线程池,例如:设置线程名字,捕获异常机制。
ThreadFactoryBuilder factoryBuilder = new ThreadFactoryBuilder();
//自定义线程名字、处理异常
ThreadFactoryBuilder factoryBuilder = new ThreadFactoryBuilder();
ThreadFactory factory = factoryBuilder.setNameFormat("自定义线程-%d")
.setUncaughtExceptionHandler((thread, throwable) -> {
LOG.error("thread {} exception", thread, throwable.getCause());
})
.build();
//其次,将线程工厂传入创建线程池方法中,将上面的方法变成
return new ThreadPoolExecutor(10, 20,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), factory);
对于捕获异常,大家需 要思考submit(),execute()方法是否能够生效。
下面看下两种不同任务的提交方式,是如何处理异常。先简单揭晓两者的区别:execute()对于自定义异常是有效的
submit()对于自定义异常是无效的
-
execute()
捕获异常是生效的,设置UncaughtExceptionHandler可以处理记录线程内未捕获的异常。 -
submit()
看下submit()方法是如何执行任务呢?
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
从源码中可以看到submit(runnable)提交任务时,submit方法会将我们的Runnable转换为RunnableFuture对象,这个对象实际上是FutureTask实例,然后将这个FutureTask交给execute方法执行。具体类图如下:
从类图中可以看到FutureTask实现RUnnable接口,故看下FutureTask中run()
方法的逻辑
可以明确看到run()方法内部捕获了call()方法的异常,并重新设置异常。
设置UncaughtExceptionHandler 是无效的,因为该函数返回一个Future<T>
的对象,如果线程执行过程中有未捕获异常,会被包在Future对象中,不会抛出异常。对返回的Future调用get()方法
的时候,在get()方法重新抛出包装之后的ExecutionException
。这个异常内部包含线程执行过程抛出的异常。这里的思路是 线程执行的异常,也是返回值的一部分,由获取返回值的时候再次抛出。
- 线程池是否需要关闭
理论上任务执行结束以后,记得将我们线程池关闭。如果线程池交给Spring容器管理之后,再次关闭线程池之后会出现什么问题?溪源实践结论是任务线程无法执行,因为线程池关闭无法工作,但如何再次开启线程池,溪源还没有研究到,希望有大佬指点迷津。
那线程池到底要关闭吗?现在开始揭晓。。。
- 获取子线程执行结果
先说如果不需要子线程返回执行结果时,可以选择使用execute()或者submit()[但是不用使用thread.get()方法,会使主线程阻塞]。
对于线程池中子线程执行任务,主线程需要依据子线程的值做处理时候
示例:
lambda表达式实现
Future<Boolean> taskResult = threadPool.submit(() -> doAction(xxx));
或者实现Callable接口