如下代码所示,我使用 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
}
}
```
1
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"); }); ``` |
2
RedBeanIce OP @AllanAG
#1 谢谢!!!!!!我现在去试试。 |
3
RedBeanIce OP @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 } } ``` |
4
wysnylc 2020-09-04 18:11:06 +08:00
|
5
putaozhenhaochi 2020-09-04 18:19:07 +08:00 via Android
用有返回结果的方法控制看看
|
6
mango88 2020-09-04 18:43:32 +08:00
|
7
RedBeanIce OP @wysnylc
#4 图裂开。 |
8
RedBeanIce OP |
9
RedBeanIce OP @putaozhenhaochi
#5 求推荐,我已经人傻了。。。。。 |
10
mango88 2020-09-04 18:50:27 +08:00
|
11
RedBeanIce OP |
12
RedBeanIce OP @mango88
#10 我同时使用一个普通方法下载,然后使用的 completablefuture 下载,一前一后执行,,前面的普通方法还是 29 张,但是一到后面这个就少了 2-3 张,每次执行不等。 |
13
putaozhenhaochi 2020-09-04 19:09:06 +08:00
@RedBeanIce 哈哈 CompletableFuture 不熟
要不试试 Stream 并行: map.values().stream().parallel().forEach( v->{ System.out.println(v); }); |
14
cheng6563 2020-09-04 19:11:56 +08:00 via Android
Java 这几个 API 实在太复杂了
|
15
zhady009 2020-09-04 19:25:53 +08:00
用这个都要加个 exceptionally(tx -> {log...})
不然你找不出问题 |
16
mango88 2020-09-04 22:17:58 +08:00
奇怪了,看起来没啥问题。DownloadPictutre 方法能简单贴一下吗 ?
|
17
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(); }); 打印报错 |
18
Narcissu5 2020-09-04 23:29:59 +08:00
```java
DownloadPicture3.download(imageUrl); ``` 比较可能是这个方法有线程安全的问题。另外如果 IO 不是异步的,使用异步就没意义。如果 IO 是异步的,那么这个方法本身就应该返回的是 Future,不需要在 runAsync 。另外 runAsync 本身会使用`ForkJoinPool#commonPool()`,而 ForkJoinPool 里面是不能放异步方法的,你这样子可能把整个 JVM 都拖慢 |
19
coldear 2020-09-05 00:27:05 +08:00
add more logs to debug.
|
20
allan888 2020-09-05 00:53:36 +08:00
download 之前 random 的等几秒钟试试?有可能有的网站图片会防止太多并发的连接。
大概这样: Thread.sleep(ThreadLocalRandom.current().nextInt(0, 15000)); DownloadPicture3.download(imageUrl); |
21
isir1234 2020-09-05 15:33:30 +08:00
runAsync 后加上异常处理试试
比如 CompletableFuture.runAsync(()->xxx).exceptionally(e -> { // print exception here return null; })) |
22
RedBeanIce OP |
23
RedBeanIce OP @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); } }); } |
24
RedBeanIce OP #13
同样也会丢很多张,,,详情代码就是在上面链接代码里面,加了一个方法 |
25
amiwrong123 2020-09-06 11:16:56 +08:00
个人怀疑,是不是 ForkJoinPool#commonPool()的坑,难道是在 supplyAsync 内部提交 task 给 commonPool 的时候执行了什么奇怪的拒绝策略。
建议使用 supplyAsync(Supplier<U> supplier, Executor executor),自己给一个线程池,排除一下线程池的原因。 |
26
cs419 2020-09-06 12:41:42 +08:00
测了下代码 下载数量正确
代码贴在语雀下面了 你再试下,不行就让你同事也运行下 没准是你环境的问题 |
27
RedBeanIce OP @cs419
#26 大佬我试了一下您的方案,还是不行,不过我在 QQ 群朋友的帮助下解决了。 https://www.yuque.com/docs/share/4ba58651-ac10-46ae-9175-1c5b43ec97ec?# |
28
RedBeanIce OP 此贴 end
下面贴代码(上面的是大佬的方案,下面是测试 3 次的代码,可行) base64 aHR0cHM6Ly93d3cueXVxdWUuY29tL2RvY3Mvc2hhcmUvNGJhNTg2NTEtYWMxMC00NmFlLTkxNzUtMWM1YjQzZWM5N2VjPyM= 错误原因:错误的使用 long startTime = System.currentTimeMillis() 作为文件的名字,文件被覆盖了 解决措施:使用 AtomicLong.incrementAndGet()自增,原子性的增加然后返回的操作 另外:LongAdder 由于没有 incrementAndGet,所以只能 increment(),然后 longvalue(),这样不是原子的操作,所以也会覆盖(中间有一个版本,我没有使用 System.currentTimeMillis(),使用了 LongAdder 仍然失败了) 总结:多线程好难啊!!!! |
29
RedBeanIce OP |