Java 8 并行流需要更多时间

6

我正在尝试学习Java 8并行流。我编写了以下代码,首先使用Executor,然后使用并行流。 似乎并行流需要两倍(10秒)的时间才能完成Executor方法所需的时间(5秒)。在我看来,并行流应该显示出类似的性能。有什么想法为什么并行流需要双倍的时间呢? 我的电脑有8个核心。

/**
 * 
 */
package com.shashank.java8.parallel_stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @author pooja
 *
 */
public class Sample {

    public static int processUrl(String url) {

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Running Thread " + Thread.currentThread());
        return url.length();
    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        usingExecutor();
        usingParallelStream();
    }

    public static void usingParallelStream() {

        Date start = new Date();
        // TODO Auto-generated method stub
        int total = buildUrlsList().parallelStream().mapToInt(Sample::processUrl).reduce(0, Integer::sum);
        Date end = new Date();
        System.out.println(total);
        System.out.println((end.getTime() - start.getTime()) / 1000);

    }

    public static void usingExecutor() throws Exception {
        Date start = new Date();
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        List<Future> futures = new ArrayList<>();

        for (String url : buildUrlsList()) {
            futures.add(executorService.submit(() -> processUrl(url)));

        }

        // iterate through the future
        int total = 0;
        for (Future<Integer> future : futures) {
            total += future.get();
        }
        System.out.println(total);
        Date end = new Date();
        System.out.println((end.getTime() - start.getTime()) / 1000);

    }

    public static List<String> buildUrlsList() {
        return Arrays.asList("url1", "url2", "url3", "url4", "url5", "url6", "url7", "url8", "url9");

    }

}

将信息移动到不同进程的时间可能是瓶颈。你不会飞到法国只为了吃法国菜。你花太多时间复制数据,而不是花足够的时间做真正的工作。 - Arya McCarthy
1
好的,这与您如何进行基准测试以及您要测试什么有关。应该在这里查看:https://dev59.com/hHRB5IYBdhLWcg3wz6UK。 - Eugene
1
除此之外,我们不知道 Sample::processUrl 在做什么,所以无法真正帮助您。但很明显,在8核机器上使用100个线程的 ExecutorService 根本不好。 - Eugene
你无法保证并行流使用不同的线程来处理流的各个元素。在流中,元素的分配策略在线程之间并不容易可控。 - Jean-Baptiste Yunès
1个回答

6
解释非常简单。您有8个核心,因此parallelStream()通常可以将工作并行化为8个线程。它们立即获取任务并都休眠5秒钟。然后其中一个取下一个(第9个)任务,并再次休眠5秒钟。然后处理完成。这意味着大约需要5秒钟(8个线程)+ 5秒钟(1个线程)=总共10秒钟。但是让我们看看实际情况。我将稍微修改您的代码:
 public static int processUrl(String url) {

    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("T[" + Thread.currentThread().getId() + "] finished @[" + System.currentTimeMillis() / 1000 + "]");
    return url.length();
}

使用并行流,您可能会得到类似于以下输出:

T[1] finished @[1494267500]
T[12] finished @[1494267500]
T[17] finished @[1494267500]
T[13] finished @[1494267500]
T[14] finished @[1494267500]
T[16] finished @[1494267500]
T[11] finished @[1494267500]
T[15] finished @[1494267500]
T[12] finished @[1494267505]
36
10

请注意,同一个线程T [12] 完成了两次任务,并在完成8个任务的第一轮后5秒钟结束。
使用您创建的线程执行程序,您有100个线程。因此,9个线程每个抓取一个任务,执行时间约为5秒,因为线程池不会耗尽:
T[14] finished @[1494267783]
T[11] finished @[1494267783]
T[19] finished @[1494267783]
T[17] finished @[1494267783]
T[12] finished @[1494267783]
T[16] finished @[1494267783]
T[13] finished @[1494267783]
T[15] finished @[1494267783]
T[18] finished @[1494267783]
36
5

请注意这里没有相同ID的线程。(这不是选择固定池通用线程数的建议 :-)我只是在阐述您实际的问题。)
尝试使用调度程序,仅分配8个线程:
ExecutorService executorService = Executors.newFixedThreadPool(8);

如果线程池被用尽,那么执行时间很可能是大致相同的。如果URL只有8个而不是9个,您会注意到类似的性能。

当然,不能保证这段代码在不同环境下的行为都相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接