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

2019-09-12 09:28:23 +08:00
 aibccn

背景:在 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();
}

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

6667 次点击
所在节点    Java
30 条回复
loongwang
2019-09-12 09:47:39 +08:00
1. 你需要组装数据 service.take().get()应该不能保证顺序
2. 线程池应该用作全局变量使用
notreami
2019-09-12 09:49:00 +08:00
这个写法,刚学习线程池嘛???
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
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
2019-09-12 10:46:41 +08:00
一般业务逻辑都不会写在 controller,放到 service,线程池这么用有没有问题要看具体场景。
场景中要考虑下请求频率和并发量,现在这样每次请求都会创建一个线程池,如果并发量很大的话会频繁创建和销毁线程池。可以考虑把线程池公共出来,设定队列等。如果需要请求速度返回,但是又不频繁,可以这么干的。
l8g
2019-09-12 10:47:07 +08:00
1. 你创建局部变量的线程池,很容易导致线程耗尽,非常危险
2. 局部变量的线程池,用完必须要 Shutdown
3. 推荐 2 楼的写法
l8g
2019-09-12 10:48:05 +08:00
@l8g 2 楼的写法 JDK8 就可以了。
lihongjie0209
2019-09-12 10:51:18 +08:00
100 个并发 10000 个线程?先把线程池改为全局的
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
2019-09-12 11:17:18 +08:00
哇瑟,你们都用上 jdk11 了?
chocotan
2019-09-12 11:27:00 +08:00
CompletableFutre+1
bulbzz
2019-09-12 11:31:41 +08:00
controller 太臃肿了 线程池应该是全局的
freebird1994
2019-09-12 11:34:03 +08:00
就向楼上说的,无论接口访问频繁不频繁。线程池不要作为局部变量。然后你这样是不能保证有序的,具体可以看线程池的源码。
lastpass
2019-09-12 12:50:45 +08:00
问题不少。
1.Executors.newFixedThreadPool(10)通常是不允许使用的。原因去看阿里编码规范。
2.你这线程池是针对于单个用户的,请将线程池设置为全局的或者使用单例来共享线程池。也不要瞎用 pool.shutdown(),没事儿乱停线程池干嘛?
3.为何要使用 ExecutorCompletionService?多加个返回队列还通过 take 阻塞干啥。为何不直接 invokeAll,复杂点也可以使用 fork/join。
4.如果你想顺序返回,可以自己加个序号,获得所有数据之后排个序。
Raymon111111
2019-09-12 13:06:31 +08:00
程序设计方面的东西就不说了

主要几个问题, 第一是线程池需要是全局的, 第二不要 shutdown. 第三是拿结果都去判空, 不要 get.get 这种写法.
HENQIGUAI
2019-09-12 13:08:16 +08:00
@l8g Java8 没有 List.of /doge
l8g
2019-09-12 13:37:57 +08:00
@HENQIGUAI 嗯.. 没注意到这个 不过 List.of 的话 JDK8 也有很多替代写法...
champloo
2019-09-12 13:59:53 +08:00
都用上 JDK11 了嘛。。
ForkNMB
2019-09-12 15:51:35 +08:00
java8 就行了啊 这种时候就应该用 CompletableFuture 舒服得一匹 谁用谁知道
NoString
2019-09-12 16:51:55 +08:00
parallelStream 会带来线程安全问题,如果 HttpHelper.getRequest(uri, 300);多个的处理时间相同,在 add 的时候获取地址一样,那么肯定列表只留一个,还有 Future 是不保证顺序的,如果对顺序有要求是要重排序的。这种场景楼上说的没错,定义一个全局的线程池,用 CompletableFuture,稳的一匹。
NoString
2019-09-12 16:57:01 +08:00
@NoString #19 当然如果是添加任务,使用 for 循环 Future 的 list,是依次取出的,这点楼上没问题。如果是在任务内部执行的操作,顺序肯定是混乱的

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

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

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

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

© 2021 V2EX