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);
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.
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.