From 07d669005ded28220026c39a123cda8f403c7f12 Mon Sep 17 00:00:00 2001 From: Piotr Stawirej Date: Fri, 22 Dec 2023 15:52:05 +0100 Subject: [PATCH] docs: update documentation --- README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 85 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index cae2c18..5f7cf46 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ ## About -`Threads Collider` attempts to execute a desired action on multiple threads at the "exactly" same moment to increase the chances of manifesting issues caused by a race condition. +`Threads Collider` attempts to execute a desired action on multiple threads at the "exactly" same moment to increase the +chances of manifesting issues caused by a race condition. ## Overview ```java + @RepeatedTest(10) void Thread_safe_adding_to_list() { // Given @@ -13,8 +15,8 @@ void Thread_safe_adding_to_list() { // When try (ThreadsCollider threadsCollider = - threadsCollider().withThreadsCount(threadsCount).build()) { - + threadsCollider().withThreadsCount(threadsCount).build()) { + threadsCollider.collide(() -> list.add("bar")); // add "bar" to list multiple times simultaneously } @@ -42,6 +44,7 @@ but the following element(s) were unexpected: #### Fixed version ```java + @RepeatedTest(10) void Thread_safe_adding_to_list() { // Given @@ -49,9 +52,9 @@ void Thread_safe_adding_to_list() { int threadsCount = Runtime.getRuntime().availableProcessors(); // When - try (ThreadsCollider threadsCollider = - threadsCollider().withThreadsCount(threadsCount).build()) { - + try (ThreadsCollider threadsCollider = + threadsCollider().withThreadsCount(threadsCount).build()) { + threadsCollider.collide(() -> list.add("bar")); } @@ -62,11 +65,15 @@ void Thread_safe_adding_to_list() { ### Usage +#### Single action + - You can create `ThreadsCollider` using `ThreadsColliderBuilder`. - Use `junit5` `@RepeatedTest` annotation to run test multiple times to increase chance of manifesting concurrency issues. ```java -@RepeatedTest(100) // run test multiple times to increase chance of manifesting concurrency issues + +@RepeatedTest(100) + // run test multiple times to increase chance of manifesting concurrency issues void Adding_unique_apples_is_thread_safe() { // Given UniqueApples uniqueApples = UniqueApples.newInstance(); @@ -74,14 +81,15 @@ void Adding_unique_apples_is_thread_safe() { // When try (ThreadsCollider threadsCollider = // use try-with-resources to automatically shutdown threads collider - threadsCollider() - .withAvailableProcessors() // preferred; or withThreadsCount(CUSTOM_THREADS_COUNT) - .withThreadsExceptionsConsumer(exceptions::add) // optional threads exceptions consumer, default do nothing - .withAwaitTerminationTimeout(10) // optional, default 60 seconds - .asSeconds() // optional - related only to "withAwaitTerminationTimeout()", default TimeUnit.SECONDS - .build()) { - - threadsCollider.collide(() -> uniqueApples.add(RED_DELICIOUS)); // <-- code to be executed simultaneously at "exactly" same moment + threadsCollider() + .withAvailableProcessors() // preferred; or withThreadsCount(CUSTOM_THREADS_COUNT) + .withThreadsExceptionsConsumer(exceptions::add) // optional threads exceptions consumer, default do nothing + .withAwaitTerminationTimeout(10) // optional, default 60 seconds + .asSeconds() // optional - related only to "withAwaitTerminationTimeout()", default TimeUnit.SECONDS + .build()) { + + threadsCollider.collide( + () -> uniqueApples.add(RED_DELICIOUS)); // <-- code to be executed simultaneously at "exactly" same moment } // Then @@ -89,10 +97,69 @@ void Adding_unique_apples_is_thread_safe() { } ``` -#### Detailed examples: +#### Multiple actions + +- When you need to execute multiple different actions simultaneously, you can use `MultiThreadsCollider` instead + of `ThreadsCollider`. +- We have thread safe `Counter` class: + +```java +public class Counter { + + private final AtomicInteger counter = new AtomicInteger(0); + + public void increment() { + counter.incrementAndGet(); + } + + public void decrement() { + counter.decrementAndGet(); + } + + public int value() { + return counter.get(); + } +} +``` + +```java +private static final int ACTION_THREADS_COUNT = Runtime.getRuntime().availableProcessors() / 2; + +@RepeatedTest(10) + // run test multiple times to increase chance of manifesting concurrency issues +void Thread_safe_counter() { + // Given + Counter counter = new Counter(); + List exceptions = new ArrayList<>(); + + // When + try (MultiThreadsCollider threadsCollider = + multiThreadsCollider() + .withAction(counter::increment) // first action to be executed simultaneously + .times(ACTION_THREADS_COUNT) // set number of threads to execute first action + .withAction(counter::decrement) // second action to be executed simultaneously + .times(ACTION_THREADS_COUNT) // set number of threads to execute second action + .withThreadsExceptionsConsumer( + exceptions::add) // optional threads exceptions consumer, default do nothing + .build()) { + + threadsCollider.collide(); + } + + // Then + then(counter.value()).isZero(); + then(exceptions).isEmpty(); +} +``` + +> **_NOTE:_** Although `withThreadsExceptionsConsumer()` method is optional, it is recommended to use it in assertions to be +> sure that no exceptions were thrown during threads execution. + +#### Detailed examples: - [ThreadsCollider_Scenarios.java](src%2Ftest%2Fjava%2Fpl%2Famazingcode%2Fthreadscollider%2FThreadsCollider_Scenarios.java) - [UseCases_Scenarios.java](src%2Ftest%2Fjava%2Fpl%2Famazingcode%2Fthreadscollider%2FUseCases_Scenarios.java) +- [MultiThreadsCollider_Scenarios.java](src%2Ftest%2Fjava%2Fpl%2Famazingcode%2Fthreadscollider%2Fmulti%2FMultiThreadsCollider_Scenarios.java) ## Requirements @@ -109,7 +176,7 @@ void Adding_unique_apples_is_thread_safe() { pl.amazingcode threads-collider - 1.0.1 + 1.0.2 test ``` @@ -117,5 +184,5 @@ void Adding_unique_apples_is_thread_safe() { ### Gradle ```groovy -testImplementation group: 'pl.amazingcode', name: 'threads-collider', version: "1.0.1" +testImplementation group: 'pl.amazingcode', name: 'threads-collider', version: "1.0.2" ``` \ No newline at end of file