Java 17’s parallelStream() causes critical performance issues with code that runs fine in Java 16. Why?

We all know or be told that creating a new thread is a heavy operation. But it seems okay to me after ran a few tests. For example: Here is the memory usage by run below simple test with 10_000 thread. It took about 2 or 3 seconds on my laptop and jvm usage is about 1.5 G.

final int threadNum = 10_000;

final Callable<String> task = () -> {
    String bigString = UUID.randomUUID().toString().repeat(1000);
    assertTrue(bigString.chars().sum() > 0);

    Thread.currentThread().sleep(1000);

    return bigString;
};

final ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
final List<Future<String>> futures = new ArrayList<>(threadNum);

for (int i = 0; i < threadNum; i++) {
    futures.add(executorService.submit(task));
}

long ret = futures.stream().map(Fn.futureGet()).mapToInt(String::length).sum();
System.out.println(ret);
assertEquals(UUID.randomUUID().toString().length() * threadNum * 1000, ret);

enter image description here

I think it’s a rare chance that 10_000 will be created/used in most of applications. If I changed the thread number to 1000. Again it took 2 or 3 seconds and memory usage is about: 300 MB.
enter image description here

Is it possible or a good idea to use stream api to run blocking I/O call in parallel? I think so. Here is a sample with my tool: abacus-common

// Run above task by Stream.
ret = IntStreamEx.range(0, threadNum)
        .parallel(threadNum)
        .mapToObj(it -> Try.call(task))
        .sequential()
        .mapToInt(String::length)
        .sum();

// Or other  task
StreamEx.of(list)
        .parallel(64) // Specify the concurrent thread number. It could be from 1 up to thousands.
        .map(this::callRestServiceToGetSomeData)
        .collect(Collectors.toUnmodifiableList());

Or use Virtual Threads introduced in Java 19+

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    StreamEx.of(list)
    .parallel(executor)
    .map(this::callRestServiceToGetSomeData)
    .collect(Collectors.toUnmodifiableList());
}

I know this is not a direct answer to the question. But it may resolve the original problem which brought up this question.

Leave a Comment