Java parallelStream不使用预期的线程数

时间:2017-02-28 17:29:49

标签: java multithreading java-8 java-stream

Java 8 parallelStream似乎使用的线程数多于系统属性 java.util.concurrent.ForkJoinPool.common.parallelism 指定的线程数。这些单元测试显示我使用自己的ForkJoinPool使用所需数量的线程处理任务,但是当使用parallelStream时,线程数高于预期。

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.Assert.assertTrue;

public class ParallelStreamTest {

    private static final int TOTAL_TASKS = 1000;

    @Test
    public void testParallelStreamWithParallelism1() throws InterruptedException {
        final Integer maxThreads = 1;
        System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", maxThreads.toString());
        List<Integer> objects = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            objects.add(i);
        }

        final AtomicInteger concurrentThreads = new AtomicInteger(0);
        final AtomicInteger taskCount = new AtomicInteger(0);

        objects.parallelStream().forEach(i -> {
            processTask(concurrentThreads, maxThreads); //expected to be called one at the time
            taskCount.addAndGet(1);
        });

        assertTrue(taskCount.get() == TOTAL_TASKS);
    }

    @Test
    public void testMyOwnForkJoinPoolWithParallelism1() throws InterruptedException {
        final Integer threads = 1;
        List<Integer> objects = new ArrayList<>();
        for (int i = 0; i < TOTAL_TASKS; i++) {
            objects.add(i);
        }

        ForkJoinPool forkJoinPool = new ForkJoinPool(1);
        final AtomicInteger concurrentThreads = new AtomicInteger(0);
        final AtomicInteger taskCount = new AtomicInteger(0);

        forkJoinPool.submit(() -> objects.parallelStream().forEach(i -> {
            processTask(concurrentThreads, threads); //expected to be called one at the time
            taskCount.addAndGet(1);
        }));
        forkJoinPool.shutdown();
        forkJoinPool.awaitTermination(1, TimeUnit.MINUTES);

        assertTrue(taskCount.get() == TOTAL_TASKS);
    }

    /**
     * It simply processes a task increasing first the concurrentThreads count
     *
     * @param concurrentThreads Counter for threads processing tasks
     * @param maxThreads Maximum number of threads that are expected to be used for processing tasks
     */
    private void processTask(AtomicInteger concurrentThreads, int maxThreads) {
        int currentConcurrentThreads = concurrentThreads.addAndGet(1);
        if (currentConcurrentThreads > maxThreads) {
            throw new IllegalStateException("There should be no more than " + maxThreads + " concurrent thread(s) but found " + currentConcurrentThreads);
        }

        // actual processing would go here

        concurrentThreads.decrementAndGet();
    }
}

只有一个线程用于处理任务,因为ForkJoinPool有parallelism=1java.util.concurrent.ForkJoinPool.common.parallelism=1。因此,两个测试都应该通过,但 testParallelStreamWithParallelism1 会失败:

  

java.lang.IllegalStateException:应该有不超过1个并发线程但找到2

设置 java.util.concurrent.ForkJoinPool.common.parallelism = 1 似乎没有按预期工作,并且同时处理了多个并发任务。

有什么想法吗?

4 个答案:

答案 0 :(得分:3)

Fork / Join池的并行性设置决定了池工作线程的数量,但是由于调用者线程,例如主线程也将在作业上工作,使用公共池时总会有一个线程。这就是default setting of the common pool is “number of cores minus one”获得实际工作线程数等于核心数的原因。

使用自定义Fork / Join池,流操作的调用者线程已经是池的工作线程,因此,利用它来处理作业不会增加实际工作线程数。

必须强调的是,Stream实现和Fork / Join池之间的交互完全没有指定,因为流使用Fork / Join框架的事实是一个实现细节。无法保证更改默认池的属性对流有任何影响,也不保证在自定义Fork / Join池的任务中调用流操作将使用该自定义池。

答案 1 :(得分:1)

同样设置此参数:

    System.setProperty("java.util.concurrent.ForkJoinPool.common.maximumSpares", "0");

这对我有用。显然(虽然没有很好的文档记录),允许“备用”线程从默认的ForkJoinPool中获取工作。

答案 2 :(得分:1)

运行此示例:

if (CheckAllProperties(user))
{

}

当您使用参数 java.util.concurrent.ForkJoinPool.common.parallelism = 1 时,您会看到类似

的内容
  IntStream.rangeClosed(0,9).parallel().forEach((i) -> {
      try {
        System.out.println("id - " + Thread.currentThread().getName());
      } catch (Exception e) {
      }
    });

正如您现在所知道的,Stream使用常见的ForkJoinPool(并列加法= 1),此外它们也使用当前线程。

答案 3 :(得分:0)

您在第一次发布此问题时删除了正确答案,因此我将对其进行阐述和扩展。你的问题在这里: int currentConcurrentThreads = concurrentThreads.addAndGet(1); 在这里:

objects.parallelStream().forEach(i -> {
  processTask(concurrentThreads, maxThreads); //expected to be called one at the time
  taskCount.addAndGet(1);
});

并行流中的每个线程都会调用processTask。因此每个都增加concurrentThreads(但由于某种原因没有 https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html#incrementAndGet-- )。由于每个都是并行运行的,所以在任何可以递减之前它们都会递增concurrentThreads。所以当然你超过了你期望的线程数。