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

Future.get() 有没有可以在等待结果的时候不堵塞当前线程?

  •  
  •   aoscici2000 · 2019-05-28 18:31:55 +08:00 · 3318 次点击
    这是一个创建于 2006 天前的主题,其中的信息可能已经有所发展或是发生改变。
    
    @RestController
    public class TestController {
    
    	@Resource(name = "taskExecutor")
    	private ThreadPoolTaskExecutor executor;
    
    	@GetMapping("/test")
    	public Map<String, Object> test() throws Exception{
        
    		Map<String, Object> map = new HashMap<>();
    
    		DemoTask task = new DemoTask();
            
    		Future<String> result = executor.submit(task);
    
    		map.put("result", result.get());
            
    		return map;
    	}
    }
    
    class DemoTask implements Callable<String> {
    
    	@Override
        public String call() throws Exception {
    		Thread.sleep(5000);
    		return "这是要的结果";
    	}
    
    }
    
    

    如果两个人几乎同时访问 /test 的时候, 怎么样可以让这两个请求同时处理, 而不需要先等第一个处理完? 假设是需要等待返回结果的.

    22 条回复    2019-05-31 23:02:34 +08:00
    pursuer
        2
    pursuer  
       2019-05-28 19:16:20 +08:00
    不想 Future.get()阻塞就在 task 最后加个回调吧
    cyhulk
        3
    cyhulk  
       2019-05-28 19:21:03 +08:00
    你可以考虑 spring 的异步相应,直接返回 callable,servelet 3.1 规范支持
    zrc
        4
    zrc  
       2019-05-28 19:31:58 +08:00
    1. 两个人同时访问 /test 不应该就是两个线程吗( springmvc 处理),除非你的逻辑里面要限制只能一定数量的线程同时访问(信号量)
    2. 如果只是为了子线程不影响主线程的执行,并且子线程的结果不需要给客户端返回,那主线程不需要进行 get 操作,如果需要进行返回那等主线程执行到必须要子线程结果的时候,那不是必须要阻塞到子线程的数据吗,否则是没有意义的吧
    3. 如果是多个子线程,主线程要等多个都返回,那么可以使用 countdownlatch
    micean
        5
    micean  
       2019-05-28 19:34:52 +08:00
    class DemoTask implements Runnable{
    final HttpServletResponse response;

    // 省略构造函数


    @Override
    public void run() {
    String 结果 = ...;
    response.getWriter().write(结果)
    }

    }
    HuHui
        6
    HuHui  
       2019-05-28 19:38:48 +08:00 via Android
    你这个需求 Spring Cloud 中的请求合并了解下
    godoway
        7
    godoway  
       2019-05-28 21:57:20 +08:00 via Android
    看看 CompletableFuture 是不是你想要的
    godoway
        8
    godoway  
       2019-05-28 22:07:55 +08:00 via Android
    CompletableFuture + DeferredResult
    创建一个 DeferredResult,然后在 CompletableFuture 的回调里面 setResult,最后返回 DeferredResult
    dengtongcai
        9
    dengtongcai  
       2019-05-28 22:39:15 +08:00 via iPhone
    CompletableFuture
    beidounanxizi
        10
    beidounanxizi  
       2019-05-28 23:35:09 +08:00
    可以用闭锁 还是栅栏 实现 ?随口一说 2 个都用来控制多线程何时处理的
    aoscici2000
        11
    aoscici2000  
    OP
       2019-05-29 11:57:36 +08:00
    @godoway 好像还是不行, 都是早早就返回了空结果.
    nekoneko
        12
    nekoneko  
       2019-05-29 15:49:05 +08:00
    看一下,CountDownLatch 或者 CyclicBarrier 吧
    aoscici2000
        13
    aoscici2000  
    OP
       2019-05-29 22:14:48 +08:00
    @godoway 这个能说得详细一点不, 这两货看了一天, 单独分开看好像挺多例子都能看得懂一点, 但结合起来就蒙了...尤其结合到例子里的实际需求的时候...
    godoway
        14
    godoway  
       2019-05-29 23:20:18 +08:00
    @aoscici2000 把 tomcat 的 max-threads 设置为 1,看看日志是不是你想要的那种

    ```
    @GetMapping("defer")
    fun testDeferred(): DeferredResult<String> {
    log.info("handle request")
    val defer = DeferredResult<String>()
    CompletableFuture.runAsync {
    Thread.sleep(1000)
    log.info("async task")
    defer.setResult("Hello defer")
    }
    log.info("defer result")
    return defer
    }
    ```
    aoscici2000
        15
    aoscici2000  
    OP
       2019-05-30 01:13:49 +08:00
    @godoway 依然还是回到了那个老问题, 只要我需要拿到最终结果, handler 还是得排队来.
    ```java

    @GetMapping("/task")
    public DeferredResult<String> ast() {

    DeferredResult<String> defer = new DeferredResult<>();

    CompletableFuture.runAsync(new Runnable() {
    @Override
    public void run() {
    Thread.sleep(5000);
    defer.setResult("结果");
    });

    return defer;
    }

    ```
    好像有点理解不来,,,
    godoway
        16
    godoway  
       2019-05-30 07:45:12 +08:00 via Android
    @aoscici2000 max-threads=1 意味着只有一个线程处理请求,那么同时来 2 个请求的时候,如果使用你之前用的 future 就会阻塞了这唯一一条线程,结果就是 2 个请求合计使用了 10 秒。但使用 CompletableFuture + DeferredResult 时,2 个请求进来了,第一个请求直接返回了 defer 随意该线程已经完成了第一个请求了,接下来就会直接处理第二个请求。而 CompletableFuture 则是在另外一个线程池执行,当他完成时就返回给用户,于是这时的结果是两个请求合计使用 5 秒多一点时间(取决于你 CompletableFuture 的线程池大小,默认是 CPU-1,如果=1 时,也是需要 10 秒了)
    aoscici2000
        17
    aoscici2000  
    OP
       2019-05-30 10:55:09 +08:00
    @godoway 运行结果看起来似乎是每个线程不同, 但等结果返回时还是得一个个来
    ```java

    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor executor;

    @GetMapping("/async-task")
    public DeferredResult<String> ast() {

    System.out.println("handler 调用, 当前线程: %s, 当前时间: %s");

    DeferredResult<String> defer = new DeferredResult<>();

    CompletableFuture.runAsync(new Runnable() {
    @Override
    public void run() {
    Thread.sleep(5000);
    defer.setResult("这是结果, 当前线程: %s, 当前时间: %s");
    System.out.println("这是结果, 当前线程: %s, 当前时间: %s");
    }, executor);

    return defer;
    }

    ```

    1 秒内两次请求 /async-task

    handler 调用, 当前线程: 35, 当前时间: 41:07:277
    handler 调用, 当前线程: 42, 当前时间: 41:12:460

    这是结果, 当前线程: 50, 当前时间: 41:12:304
    这是结果, 当前线程: 51, 当前时间: 41:17:470
    godoway
        18
    godoway  
       2019-05-30 11:48:17 +08:00 via Android
    @aoscici2000 看起来你的 35 和 42 就相差了 5 秒,确定你的测试用例正确么。
    aoscici2000
        19
    aoscici2000  
    OP
       2019-05-30 12:25:56 +08:00
    @godoway 我就简单装着两个人同时访问, 打开两个 localhost/async-task 页面 1 秒内各自刷新了一下, 我想要达到的效果是第二个应该 41:13 就处理了, 41:18 就出结果了
    godoway
        20
    godoway  
       2019-05-30 14:56:07 +08:00   ❤️ 1
    @aoscici2000
    ![](//i.imgur.com/L6S5laj.png)
    这是 tomcat 只开一个线程,另外一个 worker 线程是 10 个的情况,
    由日志可以看出,同时提交 5 个请求,异步的操作是 5 秒后才返回 response。
    并不存在你描述的问题,所以你确认你的测试是正确的吗?
    aoscici2000
        21
    aoscici2000  
    OP
       2019-05-30 16:56:46 +08:00
    @godoway 图打不开, 跳转到其他页面了...
    tomcat 这设置了的话, 除了 handler 那的线程 id 就是相同的, 但执行起来结果也是一样

    server:
    tomcat:
    max-threads: 1

    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(8);
    executor.setMaxPoolSize(16);
    aoscici2000
        22
    aoscici2000  
    OP
       2019-05-31 23:02:34 +08:00
    @godoway 晕死了, 好像是浏览器的大坑...一直以为就在 chrome 里打开两个标签刷一下就好了, 结果被人提醒用脚本测一下就完全 OK 了, 谢谢了哈, 打扰了那么就哈哈
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1992 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 00:21 · PVG 08:21 · LAX 16:21 · JFK 19:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.