【Java线程】复盘线程池使用及思考

系统开发过程中遇到了线程池的使用,这篇文章主要记录一下线程池使用过程中遇到的问题和思考。
【Java线程】复盘线程池使用及思考

  1. 自定义线程池
  • 自定义线程池
    对于如何自定义线程池以及参数设置,请移步溪源《“打工人”初识线程池及自定义线程池实战》

  • 注入Spring容器中
    为什么要放入Spring容器中呢???思考如果不放入容器中,会存在什么问题。—资源浪费。如果不放入容器中,每次执行任务时都会创建线程池,执行完任务再关闭线程池。如果任务请求次数很多,便会创建很多线程池,岂不是造成很大的资源浪费,故将其放入容器中管理。
    示例:

	@PostConstruct
    @Bean(name = "myThreadPool")
    private ThreadPoolExecutor createThreadPool() {
        return new ThreadPoolExecutor(5, 10,
                0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));
    }
  1. 线程工厂
    其实实现线程工厂接口,也是为了定制化自己的线程池,例如:设置线程名字,捕获异常机制。
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方法执行。具体类图如下:
【Java线程】复盘线程池使用及思考
从类图中可以看到FutureTask实现RUnnable接口,故看下FutureTask中run()方法的逻辑
【Java线程】复盘线程池使用及思考
可以明确看到run()方法内部捕获了call()方法的异常,并重新设置异常。

设置UncaughtExceptionHandler 是无效的,因为该函数返回一个Future<T>的对象,如果线程执行过程中有未捕获异常,会被包在Future对象中,不会抛出异常。对返回的Future调用get()方法的时候,在get()方法重新抛出包装之后的ExecutionException。这个异常内部包含线程执行过程抛出的异常。这里的思路是 线程执行的异常,也是返回值的一部分,由获取返回值的时候再次抛出。

  1. 线程池是否需要关闭

理论上任务执行结束以后,记得将我们线程池关闭。如果线程池交给Spring容器管理之后,再次关闭线程池之后会出现什么问题?溪源实践结论是任务线程无法执行,因为线程池关闭无法工作,但如何再次开启线程池,溪源还没有研究到,希望有大佬指点迷津。

那线程池到底要关闭吗?现在开始揭晓。。。

  1. 获取子线程执行结果
    先说如果不需要子线程返回执行结果时,可以选择使用execute()或者submit()[但是不用使用thread.get()方法,会使主线程阻塞]

对于线程池中子线程执行任务,主线程需要依据子线程的值做处理时候

示例:
lambda表达式实现

Future<Boolean> taskResult = threadPool.submit(() -> doAction(xxx));

或者实现Callable接口
【Java线程】复盘线程池使用及思考

上一篇:多线程的理解


下一篇:教你远程Debug Spark