V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
RedBeanIce
V2EX  ›  Java

CompletableFuture 使用交流(其实是困解)

  •  1
     
  •   RedBeanIce · Sep 4, 2020 · 4522 views
    This topic created in 2062 days ago, the information mentioned may be changed or developed.

    如下代码所示,我使用 CompletableFuture 进行多线程的下载

    但是我的 map 里面有 29 个图片 URL,我只得到了 26 张,所以求助大佬们,

    1,我的代码哪里有问题

    2,求助完整的 CompletableFuture 的使用方式

     private static void downloadCompletableFuture(Map<String, String> map) {
          try {
              List<CompletableFuture<Void>> futureList = new ArrayList<>();
              for (Map.Entry<String, String> stringStringEntry : map.entrySet()) {
                  // image Url
                  String imageUrl = stringStringEntry.getValue();
                  CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {
                      @Override
                      public void run() {
                          // download picture
                          DownloadPicture3.download(imageUrl);
                      }
                  });
                  futureList.add(future);
              }
              CompletableFuture<Void> allDoneFuture =
              		CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
              allDoneFuture.get(20, TimeUnit.SECONDS);
          } catch (Exception e) {
              e.printStackTrace();
          } finally {
              log.info("end");
              // 11:27:37.442 [main] INFO com.ice.http.JucDownloadPicture
          }
      }
      ```
    
    29 replies    2020-09-06 13:34:24 +08:00
    AllanAG
        1
    AllanAG  
       Sep 4, 2020
    既然使用了 CompletableFuture,最好使用异步的方式完成整个流程。
    1 图片下载不够,推测是超时时间太短,allDoneFuture.get(20, TimeUnit.SECONDS);20s 执行时间不够
    2 可以下面那段代码修改成这种方式试试
    ```
    CompletableFuture<Void> allDoneFuture =
    CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
    allDoneFuture.whenCompleteAsync((void1, void2) -> {
    // 所有完成回调
    log.info("end");
    });
    ```
    RedBeanIce
        2
    RedBeanIce  
    OP
       Sep 4, 2020
    @AllanAG
    #1 谢谢!!!!!!我现在去试试。
    RedBeanIce
        3
    RedBeanIce  
    OP
       Sep 4, 2020
    @AllanAG

    #1 实际上不行,whenCompleteAsync 虽然是在获得结果完成后执行,但是实际上,一张图片也没有,log 也没有打印

    ```
    private static void downloadCompletableFuture2(Map<String, String> map) {
    try {
    List<CompletableFuture<Void>> futureList = new ArrayList<>();
    for (Map.Entry<String, String> stringStringEntry : map.entrySet()) {
    // image Url
    String imageUrl = stringStringEntry.getValue();
    CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {
    @Override
    public void run() {
    // download picture
    log.info("下载所花时间 = " + DownloadPicture3.download(imageUrl));
    }
    });
    futureList.add(future);
    }
    CompletableFuture<Void> allDoneFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
    allDoneFuture.whenCompleteAsync((void1, void2) -> {
    // 所有完成回调
    log.info("================================end");
    });

    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    log.info("end");
    // 11:27:37.442 [main] INFO com.ice.http.JucDownloadPicture
    }
    }
    ```
    wysnylc
        4
    wysnylc  
       Sep 4, 2020
    收集的资料
    putaozhenhaochi
        5
    putaozhenhaochi  
       Sep 4, 2020 via Android
    用有返回结果的方法控制看看
    mango88
        6
    mango88  
       Sep 4, 2020
    @RedBeanIce

    CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();

    阻塞等待所有的完成
    RedBeanIce
        7
    RedBeanIce  
    OP
       Sep 4, 2020
    @wysnylc
    #4 图裂开。
    RedBeanIce
        8
    RedBeanIce  
    OP
       Sep 4, 2020
    @mango88

    #6 不行,仍然少了三张

    CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();
    RedBeanIce
        9
    RedBeanIce  
    OP
       Sep 4, 2020
    @putaozhenhaochi
    #5 求推荐,我已经人傻了。。。。。
    mango88
        10
    mango88  
       Sep 4, 2020
    @RedBeanIce

    少三张,可能就不是并行的原因了,也许是下载报错了吧
    RedBeanIce
        11
    RedBeanIce  
    OP
       Sep 4, 2020
    RedBeanIce
        12
    RedBeanIce  
    OP
       Sep 4, 2020
    @mango88
    #10

    我同时使用一个普通方法下载,然后使用的 completablefuture 下载,一前一后执行,,前面的普通方法还是 29 张,但是一到后面这个就少了 2-3 张,每次执行不等。
    putaozhenhaochi
        13
    putaozhenhaochi  
       Sep 4, 2020
    @RedBeanIce 哈哈 CompletableFuture 不熟

    要不试试 Stream 并行:
    map.values().stream().parallel().forEach( v->{
    System.out.println(v);
    });
    cheng6563
        14
    cheng6563  
       Sep 4, 2020 via Android
    Java 这几个 API 实在太复杂了
    zhady009
        15
    zhady009  
       Sep 4, 2020
    用这个都要加个 exceptionally(tx -> {log...})
    不然你找不出问题
    mango88
        16
    mango88  
       Sep 4, 2020
    奇怪了,看起来没啥问题。DownloadPictutre 方法能简单贴一下吗 ?
    cs419
        17
    cs419  
       Sep 4, 2020
    排查手段不详细
    既然下载有缺失
    那把那 3 个失败的单独跑一遍代码是什么结果 (排除这 3 个链接就有问题)
    再针对下载成功的 26 个链接,下载 50 次 又是啥结果 (排除任务太多)

    在方法 DownloadPicture3.download(imageUrl); 前后打印序号
    确保 download 方法 成功的确完成了 50 次调用
    如果这里没毛病,那就是 download 方法内部执行出了问题

    获取结果 allDoneFuture.get() 不使用超时时间
    allDoneFuture.exceptionally(e->{
    System.out.println("出错-"+ e.getMessage());
    e.printStackTrace();
    });
    打印报错
    Narcissu5
        18
    Narcissu5  
       Sep 4, 2020
    ```java
    DownloadPicture3.download(imageUrl);
    ```

    比较可能是这个方法有线程安全的问题。另外如果 IO 不是异步的,使用异步就没意义。如果 IO 是异步的,那么这个方法本身就应该返回的是 Future,不需要在 runAsync 。另外 runAsync 本身会使用`ForkJoinPool#commonPool()`,而 ForkJoinPool 里面是不能放异步方法的,你这样子可能把整个 JVM 都拖慢
    coldear
        19
    coldear  
       Sep 5, 2020
    add more logs to debug.
    allan888
        20
    allan888  
       Sep 5, 2020
    download 之前 random 的等几秒钟试试?有可能有的网站图片会防止太多并发的连接。
    大概这样:
    Thread.sleep(ThreadLocalRandom.current().nextInt(0, 15000));
    DownloadPicture3.download(imageUrl);
    isir1234
        21
    isir1234  
       Sep 5, 2020
    runAsync 后加上异常处理试试

    比如 CompletableFuture.runAsync(()->xxx).exceptionally(e -> {
    // print exception here
    return null;
    }))
    RedBeanIce
        22
    RedBeanIce  
    OP
       Sep 6, 2020
    @zhady009
    @mango88
    @cs419
    @Narcissu5
    @coldear
    @allan888
    @isir1234

    https://www.yuque.com/docs/share/61f38a49-764c-4b6e-9271-53e06fc0d32d?#

    各位大佬代码已经贴出来了,,大佬们可以在自己的电脑执行链接中的代码,
    大佬们的方法我都试了一下,好像不行

    1,也没有报错的 log
    2,
    RedBeanIce
        23
    RedBeanIce  
    OP
       Sep 6, 2020
    @putaozhenhaochi

    private static void downloadStream(Map<String, String> map) throws IOException {
    map.values().parallelStream().forEach(new Consumer<String>() {
    @SneakyThrows
    @Override
    public void accept(String s) {
    Long download = download(s);
    System.out.println(download);
    }
    });
    }
    RedBeanIce
        24
    RedBeanIce  
    OP
       Sep 6, 2020
    #13
    同样也会丢很多张,,,详情代码就是在上面链接代码里面,加了一个方法
    amiwrong123
        25
    amiwrong123  
       Sep 6, 2020
    个人怀疑,是不是 ForkJoinPool#commonPool()的坑,难道是在 supplyAsync 内部提交 task 给 commonPool 的时候执行了什么奇怪的拒绝策略。

    建议使用 supplyAsync(Supplier<U> supplier, Executor executor),自己给一个线程池,排除一下线程池的原因。
    cs419
        26
    cs419  
       Sep 6, 2020
    测了下代码 下载数量正确
    代码贴在语雀下面了
    你再试下,不行就让你同事也运行下
    没准是你环境的问题
    RedBeanIce
        27
    RedBeanIce  
    OP
       Sep 6, 2020
    @cs419

    #26 大佬我试了一下您的方案,还是不行,不过我在 QQ 群朋友的帮助下解决了。
    https://www.yuque.com/docs/share/4ba58651-ac10-46ae-9175-1c5b43ec97ec?#
    RedBeanIce
        28
    RedBeanIce  
    OP
       Sep 6, 2020
    此贴 end

    下面贴代码(上面的是大佬的方案,下面是测试 3 次的代码,可行) base64

    aHR0cHM6Ly93d3cueXVxdWUuY29tL2RvY3Mvc2hhcmUvNGJhNTg2NTEtYWMxMC00NmFlLTkxNzUtMWM1YjQzZWM5N2VjPyM=

    错误原因:错误的使用 long startTime = System.currentTimeMillis() 作为文件的名字,文件被覆盖了

    解决措施:使用 AtomicLong.incrementAndGet()自增,原子性的增加然后返回的操作

    另外:LongAdder 由于没有 incrementAndGet,所以只能 increment(),然后 longvalue(),这样不是原子的操作,所以也会覆盖(中间有一个版本,我没有使用 System.currentTimeMillis(),使用了 LongAdder 仍然失败了)

    总结:多线程好难啊!!!!
    RedBeanIce
        29
    RedBeanIce  
    OP
       Sep 6, 2020
    各位大佬,谢谢指教!!!!太强了

    贴一下 26 楼的大佬
    https://www.yuque.com/docs/share/6065b121-4732-4826-bac2-3bb356f0461e?#
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1142 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 58ms · UTC 18:14 · PVG 02:14 · LAX 11:14 · JFK 14:14
    ♥ Do have faith in what you're doing.