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

2020-09-04 17:00:49 +08:00
 RedBeanIce

如下代码所示,我使用 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
      }
  }
  ```
3767 次点击
所在节点    Java
29 条回复
AllanAG
2020-09-04 17:25:02 +08:00
既然使用了 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
2020-09-04 17:32:19 +08:00
@AllanAG
#1 谢谢!!!!!!我现在去试试。
RedBeanIce
2020-09-04 17:38:27 +08:00
@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
2020-09-04 18:11:06 +08:00
收集的资料
putaozhenhaochi
2020-09-04 18:19:07 +08:00
用有返回结果的方法控制看看
mango88
2020-09-04 18:43:32 +08:00
@RedBeanIce

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

阻塞等待所有的完成
RedBeanIce
2020-09-04 18:44:38 +08:00
@wysnylc
#4 图裂开。
RedBeanIce
2020-09-04 18:48:13 +08:00
@mango88

#6 不行,仍然少了三张

CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();
RedBeanIce
2020-09-04 18:48:58 +08:00
@putaozhenhaochi
#5 求推荐,我已经人傻了。。。。。
mango88
2020-09-04 18:50:27 +08:00
@RedBeanIce

少三张,可能就不是并行的原因了,也许是下载报错了吧
RedBeanIce
2020-09-04 18:55:11 +08:00
RedBeanIce
2020-09-04 18:56:58 +08:00
@mango88
#10

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

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

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

获取结果 allDoneFuture.get() 不使用超时时间
allDoneFuture.exceptionally(e->{
System.out.println("出错-"+ e.getMessage());
e.printStackTrace();
});
打印报错
Narcissu5
2020-09-04 23:29:59 +08:00
```java
DownloadPicture3.download(imageUrl);
```

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

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

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

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

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

© 2021 V2EX