[ Java ] 线程池问题疑惑,大佬们赐教 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
aibccn
V2EX    Java

[ Java ] 线程池问题疑惑,大佬们赐教

  •  
  •   aibccn 2019-09-12 09:28:23 +08:00 7325 次点击
    这是一个创建于 2220 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景:在 controller 里并发调用三个其他服务接口,然后组装数据返回

    @RequestMapping({"/flow/test"}) @ResponseBody public String hello(HttpServletRequest request, @RequestParam(name = "tabId", required = false) Integer tabId) { ExecutorService servicepool = Excutors.newFixedThreadPool(10); ExecutorCompletionService<String> service = new ExecutorCompletionService<String>(servicepool); service.submit(() -> { String uri = "http://localhost/api/user/info"; return HttpHelper.getRequest(uri, 300); }); service.submit(() -> { String uri = "http://localhost/api/news/list"; return HttpHelper.getRequest(uri, 300); }); service.submit(() -> { String uri = "http://localhost/api/flow/list"; return HttpHelper.getRequest(uri, 300); }); List<String> curlResult = new ArrayList<>(); try { servicepool.shutdown(); for (int i = 0; i < 3; i++) { curlResult.add(service.take().get()); } } catch (Exception ex) { } //TODO 组装数据 return JSON.toJSON(curlResult).toString(); } 

    问题:这样写可不可以?有什么弊端或者是有什么需要注意的地方,请大佬们指点一二

    30 条回复    2019-09-12 19:49:15 +08:00
    loongwang
        1
    loongwang  
       2019-09-12 09:47:39 +08:00
    1. 你需要组装数据 service.take().get()应该不能保证顺序
    2. 线程池应该用作全局变量使用
    notreami
        2
    notreami  
       2019-09-12 09:49:00 +08:00   4
    这个写法,刚学习线程池嘛???
    2019 年了,jdk11 的走起
    ```
    @RequestMapping({"/flow/test"})
    @ResponseBody
    public String hello(HttpServletRequest request, @RequestParam(name = "tabId", required = false) Integer tabId) {
    List<String> uriList = List.of("http://localhost/api/user/info","http://localhost/api/news/list","http://localhost/api/flow/list");
    List<String> curlResult = uriList.parallelStream().map(uri -> HttpHelper.getRequest(uri, 300)).collect(Collectors.toList());
    //TODO 组装数据
    return JSON.toJSON(curlResult);
    }
    ```
    xiaoyaojc
        3
    xiaoyaojc  
       2019-09-12 10:42:03 +08:00
    service.submit 返回的是 Future,你可以把 Future 收集起来,然后遍历 List<Future>的内容,这样出来的顺序和你添加到 list 中的顺序是一致的。List<Future> future=service.submit(() -> {

    String uri = "http://localhost/api/flow/list";
    return HttpHelper.getRequest(uri, 300);

    });然后 list.add(future)。每个都这么做,这样再去遍历 list 的时候,出来的 curlResult 就是有序的。
    18258226728
        4
    18258226728  
       2019-09-12 10:46:41 +08:00
    一般业务逻辑都不会写在 controller,放到 service,线程池这么用有没有问题要看具体场景。
    场景中要考虑下请求频率和并发量,现在这样每次请求都会创建一个线程池,如果并发量很大的话会频繁创建和销毁线程池。可以考虑把线程池公共出来,设定队列等。如果需要请求速度返回,但是又不频繁,可以这么干的。
    l8g
        5
    l8g  
       2019-09-12 10:47:07 +08:00
    1. 你创建局部变量的线程池,很容易导致线程耗尽,非常危险
    2. 局部变量的线程池,用完必须要 Shutdown
    3. 推荐 2 楼的写法
    l8g
        6
    l8g  
       2019-09-12 10:48:05 +08:00   1
    @l8g 2 楼的写法 JDK8 就可以了。
    lihongjie0209
        7
    lihongjie0209  
       2019-09-12 10:51:18 +08:00
    100 个并发 10000 个线程?先把线程池改为全局的
    isir1234
        8
    isir1234  
       2019-09-12 11:03:24 +08:00
    CompletableFuture<String> rs1 = CompletableFuture.supplyAsync(() -> callApi());
    CompletableFuture<String> rs2 = CompletableFuture.supplyAsync(() -> callApi());
    CompletableFuture<String> rs3 = CompletableFuture.supplyAsync(() -> callApi());

    List<String> results = Arrays.asList(rs1, rs2, rs3).stream().map(CompletableFuture::join).collect(Collectors.toList());
    System.out.println(results);
    Cukuyo
        9
    Cukuyo  
       2019-09-12 11:17:18 +08:00   1
    哇瑟,你们都用上 jdk11 了?
    chocotan
        10
    chocotan  
       2019-09-12 11:27:00 +08:00
    CompletableFutre+1
    bulbzz
        11
    bulbzz  
       2019-09-12 11:31:41 +08:00
    controller 太臃肿了 线程池应该是全局的
    freebird1994
        12
    freebird1994  
       2019-09-12 11:34:03 +08:00
    就向楼上说的,无论接口访问频繁不频繁。线程池不要作为局部变量。然后你这样是不能保证有序的,具体可以看线程池的源码。
        13
    lastpass  
       2019-09-12 12:50:45 +08:00 via Android
    问题不少。
    1.Executors.newFixedThreadPool(10)通常是不允许使用的。原因去看阿里编码规范。
    2.你这线程池是针对于单个用户的,请将线程池设置为全局的或者使用单例来共享线程池。也不要瞎用 pool.shutdown(),没事儿乱停线程池干嘛?
    3.为何要使用 ExecutorCompletionService?多加个返回队列还通过 take 阻塞干啥。为何不直接 invokeAll,复杂点也可以使用 fork/join。
    4.如果你想顺序返回,可以自己加个序号,获得所有数据之后排个序。
    Raymon111111
        14
    Raymon111111  
       2019-09-12 13:06:31 +08:00
    程序设计方面的东西就不说了

    主要几个问题, 第一是线程池需要是全局的, 第二不要 shutdown. 第三是拿结果都去判空, 不要 get.get 这种写法.
    HENQIGUAI
        15
    HENQIGUAI  
       2019-09-12 13:08:16 +08:00
    @l8g Java8 没有 List.of /doge
    l8g
        16
    l8g  
       2019-09-12 13:37:57 +08:00
    @HENQIGUAI 嗯.. 没注意到这个 不过 List.of 的话 JDK8 也有很多替代写法...
    champloo
        17
    champloo  
       2019-09-12 13:59:53 +08:00
    都用上 JDK11 了嘛。。
    ForkNMB
        18
    ForkNMB  
       2019-09-12 15:51:35 +08:00
    java8 就行了啊 这种时候就应该用 CompletableFuture 舒服得一匹 谁用谁知道
    NoString
        19
    NoString  
       2019-09-12 16:51:55 +08:00
    parallelStream 会带来线程安全问题,如果 HttpHelper.getRequest(uri, 300);多个的处理时间相同,在 add 的时候获取地址一样,那么肯定列表只留一个,还有 Future 是不保证顺序的,如果对顺序有要求是要重排序的。这种场景楼上说的没错,定义一个全局的线程池,用 CompletableFuture,稳的一匹。
    NoString
        20
    NoString  
       2019-09-12 16:57:01 +08:00
    @NoString #19 当然如果是添加任务,使用 for 循环 Future 的 list,是依次取出的,这点楼上没问题。如果是在任务内部执行的操作,顺序肯定是混乱的
    wysnylc
        21
    wysnylc  
       2019-09-12 17:11:00 +08:00
    @chocotan #10 +2
    changhe626
        22
    changhe626  
       2019-09-12 17:17:07 +08:00
    10 +3
    notreami
        23
    notreami  
       2019-09-12 17:50:21 +08:00
    @NoString 如果 HttpHelper.getRequest(uri, 300);多个的处理时间相同,在 add 的时候获取地址一样,那么肯定列表只留一个。
    可以具体说明下嘛?我理解 parallelStream 是有线程安全问题,但是不存在并行结果合并的情况吧。
    lazyfighter
        24
    lazyfighter  
       2019-09-12 18:26:56 +08:00
    你这写的啥啊,我艹,真的一点思考没有啊 ,拼程序啊
    guyeu
        25
    guyeu  
       2019-09-12 18:52:48 +08:00
    如果稍微有点要求的话:
    1. 数据和业务拆分;
    2. 公共线程池+异步调用返回 Future ;
    3. 对响应做缓存 /复用;
    NoString
        26
    NoString  
       2019-09-12 18:53:56 +08:00
    @notreami #23 这块是我表述的有问题,怪我没说明情况。丢失的情况是开启并行管道后 list 引发的安全问题,而本身的 forkjiontask 的处理其实不存在问题,是并行添加合并了。比如 foreach 里执行的合并操作,你这个应该没事 毕竟.collect(Collectors.toList()) 是执行一批合一批 我的我的 呜呜呜 你这个已经解决他的问题了,除了只能用公共线程池之外都挺好 点赞点赞
    guyeu
        27
    guyeu  
       2019-09-12 18:55:43 +08:00
    @NoString #19
    @NoString #20
    即使处理时间相同,在 add 的时候获取地址一样,那么列表是一个有三个相同元素的列表,parallelStream 的问题仅仅是并发导致不保证顺序,并不会帮你合并相同的任务。
    NoString
        28
    NoString  
       2019-09-12 19:23:35 +08:00
    @guyeu #27 emmm 我说的意思是这个 list1.parallelStream().forEach(oa -> list2.add(oa)); 若是 2 楼带哥的 collect(Collectors.toList()) 顺序都是稳的
    NoString
        29
    NoString  
       2019-09-12 19:24:01 +08:00
    @guyeu #27 呜呜呜 说的离谱了 我的我的
    notreami
        30
    notreami  
       2019-09-12 19:49:15 +08:00
    @NoString 吓我一跳,查了半天资料,一点头绪都没有,幸好你回复了。哈哈
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4590 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 31ms UTC 04:01 PVG 12:01 LAX 21:01 JFK 00:01
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86