问下 Java 大佬,用了 @async 为什么还要使用线程池

2022-05-19 19:56:33 +08:00
 gosidealone
    @Override
    @Async
    public void sendTemplateMsg(WxMpTemplateMessage msg,String appid) {
        TaskExcutor.submit(() -> {
            String result;
            try {
                wxService.switchover(appid);
                result = wxService.getTemplateMsgService().sendTemplateMsg(msg);
            } catch (WxErrorException e) {
                result = e.getMessage();
            }

            //保存发送日志
            TemplateMsgLog log = new TemplateMsgLog(msg,appid, result);
            templateMsgLogService.addLog(log);
        });
    }

代码如上,明明是用了 @async 注解,这个注解的本质不是使用了线程池吗?为什么代码里面还要利用线程池去执行?经过尝试,如果去除 TaskExcutor.submit(),这个函数也能异步执行的,那这么写的目的是什么呢? 这是个开源项目,具体可见 https://github.com/niefy/wx-api/blob/master/src/main/java/com/github/niefy/modules/wx/service/impl/TemplateMsgServiceImpl.java

1672 次点击
所在节点    问与答
11 条回复
dqzcwxb
2022-05-19 20:20:47 +08:00
可能 1,不想占用 @Async 的线程池
可能 2,wxService.switchover()方法也被 @Async 修饰与 sendTemplateMsg()为父子任务,在 ThreadPoolExecutor 线程池中可能导致死锁
mmdsun
2022-05-19 20:30:16 +08:00
不推荐直接用 @Async 注解。虽然现在 spring 提供了这个注解的 yml 配置可以设置大小数量等。但我还是习惯性每个任务分配个线程池的配置,然后再指定名字 @Async("yourName")
gosidealone
2022-05-19 20:32:53 +08:00
@dqzcwxb 1 的情况的话 那这样是不是没使用到 @async 的线程池? 2 的情况我不是很明白,可否详细解释下 谢谢
gosidealone
2022-05-19 20:39:24 +08:00
@mmdsun 是的 一般都是自定义线程池的 但是这里不明白为什么代码里还要手动调用线程池?
mmdsun
2022-05-19 20:50:16 +08:00
@gosidealone 这种很常见啊。举个列子:我控制器加方法加 @Async 是为了立即返回数据,为了不阻塞用户操作。但是我里面子有很多任务,10 条*10 需要分批跑数据,这种就需要再开新线程池处理。
mikicomo
2022-05-19 20:51:06 +08:00
简单看了下,作者应该是希望用 TaskExcutor 中统一管理的线程池去执行系统中的任务,那么从这点看 sendTemplateMsg 上加 @Async 的确有点画蛇添足了,TaskExcutor.submit 直接提交任务即可。

那么关于这个 @Async 注解为什么这里还加了呢,我个人猜测是,是不是第一版的时候并没有 TaskExcutor ?后来才单独抽出来改造了一版?建议可以看看 git 提交记录,这里我也没 down 代码下来看,不负责任猜测一下😂
mikicomo
2022-05-19 21:00:49 +08:00
另外,sendMsgBatch 方法中循环调用了 this.sendTemplateMsg ,注意同个类下,两个 async 方法相互调用时,@async 会失效,如果作者没有在 sendMsgBatch 另起 TaskExcutor 的话,可能和它的本意不符了( sendMsgBatch 本身虽然是异步,但是内部的循环执行降级为了同步,线程也占用了很久),现在作者的这种写法,是把压力都丢给了 TaskExcutor ,让他慢慢去执行,@async 开出的线程池立马就释放了

道理是这个道理,但是这么写,其实也不是很建议就是了
mikicomo
2022-05-19 21:08:51 +08:00
这里展开说一下,其实自己项目使用自己封装过后的线程池是个好习惯,不过如果只是为了控制线程池个数的话,那倒也大可不必,原生的配置也蛮好。
一般我们遇到自己封装线程池的场景,主要是为了传递一些系统中的参数,比如你既然是个异步任务,如果是由外部的一个请求触发的,再这样的场景下,我们做全链路日志会比较麻烦,因为原生线程池是不会传递 jvm 参数的,所以需要我们封装一下,这样就可以方便的在日志系统中通过一个 logid 搜索全链路日志了。

另一个好处是,自己封装的线程池,我们也可以方便做一些 feature 进去,比如动态扩容,缩容线程池,如果一开始都用了系统的,没有统一收口的话,就会比较麻烦
zava
2022-05-19 21:46:32 +08:00
这么做明显有问题呀!增加偶然复杂度。要不就是作者没关注这块;要不就是有其他原因,但就算有其他原因,也没有在代码上体现出原因或意图,可读性明显有问题。这不,题主就被阔绕有疑惑,跑来问了。

这代码要在我这里 review ,是会被我说的...
gosidealone
2022-05-19 21:59:17 +08:00
所以我觉得应该是作者可能没注意,谢谢大佬们的回复
@mikicomo
@zava
@mmdsun
dqzcwxb
2022-05-19 23:50:50 +08:00
@gosidealone #3 1 使用了 @Async 的线程,但是只是走个过场而已
2 的死锁和处理方案看这个 https://yanbin.blog/common-threadpool-vs-forkjoinpool/

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/853995

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX