V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
gosidealone
V2EX  ›  问与答

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

  •  
  •   gosidealone · 2022-05-19 19:56:33 +08:00 · 1814 次点击
    这是一个创建于 905 天前的主题,其中的信息可能已经有所发展或是发生改变。
        @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

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

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

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

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

    这代码要在我这里 review ,是会被我说的...
    gosidealone
        10
    gosidealone  
    OP
       2022-05-19 21:59:17 +08:00
    所以我觉得应该是作者可能没注意,谢谢大佬们的回复
    @mikicomo
    @zava
    @mmdsun
    dqzcwxb
        11
    dqzcwxb  
       2022-05-19 23:50:50 +08:00
    @gosidealone #3 1 使用了 @Async 的线程,但是只是走个过场而已
    2 的死锁和处理方案看这个 https://yanbin.blog/common-threadpool-vs-forkjoinpool/
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1866 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 16:28 · PVG 00:28 · LAX 08:28 · JFK 11:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.