如下代码所示,我使用 CompletableFuture 进行多线程的下载
但是我的 map 里面有 29 个图片 URL,我只得到了 26 张,所以求助大佬们,
1,我的代码哪里有问题
2,求助完整的 CompletableFuture 的使用方式
private static void downloadCompletableFutre(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 |