ConcurrentRunner
Class ConcurrentRunner provides an easy-to-use API for running code concurrently. It can work with both Executable and ThrowingSupplier for the code to run concurrently.
Working with ThrowingSupplier
The most common use with ThrowingSupplier
is as follows, where each code block returns an instance of MyResult
:
List<MyResult> results = ConcurrentRunner.running(() -> firstCodeBlock())
.concurrentlyWith(() -> secondCodeBlock())
.execute()
.andListResults();
Running the same block of code several times
Both running
and concurrentlyWith
are overloaded to accept a number of times to call the Executable
or ThrowableSupplier
. For instance, the following will run the same code block 10 times:
List<MyResult> results = ConcurrentRunner.running(() -> codeBlock(), 10)
.execute()
.andListResults();
Working with Executable
The andListResults()
methods collects all results into a list. When working only with Executable
, the results will always be null
, so collecting these into a List<Void>
wouldn't add much value. The andAssertNoFailures()
method can be used instead to process the results without collecting them:
ConcurrentRunner.running(() -> codeBlock(), 10)
.execute()
.andAssertNoFailures();
As a shortcut to calling execute().andAssertNoFailures()
, some static methods have been added that work with Executable
only:
// Run the same code block 10 times
ConcurrentRunner.runConcurrently(() -> codeBlock(), 10);
// Run different code blocks:
ConcurrentRunner.runConcurrently(
() -> firstCodeBlock(),
() -> secondCodeBlock()
);
// The above is equivalent to:
ConcurrentRunner.runConcurrently(Arrays.asList(
() -> firstCodeBlock(),
() -> secondCodeBlock()
));
Handling errors and exceptions
Any error or exception thrown from an Executable
or ThrowingSupplier
is thrown when results are evaluated, including when calling the static runConcurrently
methods. If the code to run concurrently may throw exceptions, make sure to handle those inside the Executable
or ThrowingSupplier
, e.g. using assertThrows.
Handling results
Besides the andListResults()
and andAssertNoFailures()
methods mentioned earlier, the object returned by the execute()
method has some more methods that expose the results:
andStreamResults()
returns a stream that lazily evaluates each result. If anyExecutable
orThrowingSupplier
threw an error or exception, the stream will throw an error or exception if a terminal operation is executed.andCollectResults(Collector)
is a general purpose version ofandListResults()
that can take anyCollector
. LikeandListResults()
andandAssertNoFailures()
, every result is evaluated.
Thread count
By default, one thread for each provided Executable
or ThrowingSupplier
is used, and each is called at approximately the same time. By calling withThreadCount
it's possible to use a lower number of threads. This allows testing that code that uses lazy initialization works fine if initialization has already taken place; some of the provided Executables
or ThrowingSuppliers
will be called after at least one other has already finished.
If you want to call ConcurrentRunner.runConcurrently
in your tests with the same concurrency settings that include a custom thread count, you can use the overloaded version that takes shared a ConcurrencySettings instance:
private static final ConcurrencySettings CONCURRENCY_SETTINGS = ConcurrencySettings.withCount(100).withThreadCount(50);
@Test
void testMyCode() {
runConcurrently(() -> {
// perform the actual test
}, CONCURRENCY_SETTINGS);
}