首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  Java

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

  •  
  •   aibccn · 3 天前 · 2225 次点击

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

    @RequestMapping({"/flow/test"})
    @ResponseBody
    public String hello(HttpServletRequest request, @RequestParam(name = "tabId", required = false) Integer tabId) {
    
    
        ExecutorService servicepool = Executors.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
        1
    loongwang   3 天前
    1. 你需要组装数据 service.take().get()应该不能保证顺序
    2. 线程池应该用作全局变量使用
        2
    notreami   3 天前   ♥ 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);
    }
    ```
        3
    xiaoyaojc   3 天前
    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 就是有序的。
        4
    18258226728   3 天前
    一般业务逻辑都不会写在 controller,放到 service,线程池这么用有没有问题要看具体场景。
    场景中要考虑下请求频率和并发量,现在这样每次请求都会创建一个线程池,如果并发量很大的话会频繁创建和销毁线程池。可以考虑把线程池公共出来,设定队列等。如果需要请求速度返回,但是又不频繁,可以这么干的。
        5
    l8g   3 天前
    1. 你创建局部变量的线程池,很容易导致线程耗尽,非常危险
    2. 局部变量的线程池,用完必须要 Shutdown
    3. 推荐 2 楼的写法
        6
    l8g   3 天前   ♥ 1
    @l8g 2 楼的写法 JDK8 就可以了。
        7
    lihongjie0209   3 天前
    100 个并发 10000 个线程?先把线程池改为全局的
        8
    isir1234   3 天前
    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);
        9
    Cukuyo   3 天前   ♥ 1
    哇瑟,你们都用上 jdk11 了?
        10
    chocotan   3 天前
    CompletableFutre+1
        11
    bulbzz   3 天前
    controller 太臃肿了 线程池应该是全局的
        12
    freebird1994   3 天前
    就向楼上说的,无论接口访问频繁不频繁。线程池不要作为局部变量。然后你这样是不能保证有序的,具体可以看线程池的源码。
        13
    lastpass   3 天前 via Android
    问题不少。
    1.Executors.newFixedThreadPool(10)通常是不允许使用的。原因去看阿里编码规范。
    2.你这线程池是针对于单个用户的,请将线程池设置为全局的或者使用单例来共享线程池。也不要瞎用 pool.shutdown(),没事儿乱停线程池干嘛?
    3.为何要使用 ExecutorCompletionService?多加个返回队列还通过 take 阻塞干啥。为何不直接 invokeAll,复杂点也可以使用 fork/join。
    4.如果你想顺序返回,可以自己加个序号,获得所有数据之后排个序。
        14
    Raymon111111   3 天前
    程序设计方面的东西就不说了

    主要几个问题, 第一是线程池需要是全局的, 第二不要 shutdown. 第三是拿结果都去判空, 不要 get.get 这种写法.
        15
    HENQIGUAI   3 天前
    @l8g Java8 没有 List.of /doge
        16
    l8g   3 天前
    @HENQIGUAI 嗯.. 没注意到这个 不过 List.of 的话 JDK8 也有很多替代写法...
        17
    champloo   3 天前
    都用上 JDK11 了嘛。。
        18
    ForkNMB   3 天前
    java8 就行了啊 这种时候就应该用 CompletableFuture 舒服得一匹 谁用谁知道
        19
    NoString   3 天前
    parallelStream 会带来线程安全问题,如果 HttpHelper.getRequest(uri, 300);多个的处理时间相同,在 add 的时候获取地址一样,那么肯定列表只留一个,还有 Future 是不保证顺序的,如果对顺序有要求是要重排序的。这种场景楼上说的没错,定义一个全局的线程池,用 CompletableFuture,稳的一匹。
        20
    NoString   3 天前
    @NoString #19 当然如果是添加任务,使用 for 循环 Future 的 list,是依次取出的,这点楼上没问题。如果是在任务内部执行的操作,顺序肯定是混乱的
        21
    wysnylc   3 天前
    @chocotan #10 +2
        22
    changhe626   3 天前
    10 +3
        23
    notreami   3 天前
    @NoString 如果 HttpHelper.getRequest(uri, 300);多个的处理时间相同,在 add 的时候获取地址一样,那么肯定列表只留一个。
    可以具体说明下嘛?我理解 parallelStream 是有线程安全问题,但是不存在并行结果合并的情况吧。
        24
    lazyfighter   3 天前
    你这写的啥啊,我艹,真的一点思考没有啊 ,拼程序啊
        25
    guyeu   3 天前
    如果稍微有点要求的话:
    1. 数据和业务拆分;
    2. 公共线程池+异步调用返回 Future ;
    3. 对响应做缓存 /复用;
        26
    NoString   3 天前
    @notreami #23 这块是我表述的有问题,怪我没说明情况。丢失的情况是开启并行管道后 list 引发的安全问题,而本身的 forkjiontask 的处理其实不存在问题,是并行添加合并了。比如 foreach 里执行的合并操作,你这个应该没事 毕竟.collect(Collectors.toList()) 是执行一批合一批 我的我的 呜呜呜 你这个已经解决他的问题了,除了只能用公共线程池之外都挺好 点赞点赞
        27
    guyeu   3 天前
    @NoString #19
    @NoString #20
    即使处理时间相同,在 add 的时候获取地址一样,那么列表是一个有三个相同元素的列表,parallelStream 的问题仅仅是并发导致不保证顺序,并不会帮你合并相同的任务。
        28
    NoString   3 天前
    @guyeu #27 emmm 我说的意思是这个 list1.parallelStream().forEach(oa -> list2.add(oa)); 若是 2 楼带哥的 collect(Collectors.toList()) 顺序都是稳的
        29
    NoString   3 天前
    @guyeu #27 呜呜呜 说的离谱了 我的我的
        30
    notreami   3 天前
    @NoString 吓我一跳,查了半天资料,一点头绪都没有,幸好你回复了。哈哈
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   884 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 25ms · UTC 17:56 · PVG 01:56 · LAX 10:56 · JFK 13:56
    ♥ Do have faith in what you're doing.