diff --git a/src/main/java/pl/amazingcode/threadscollider/multi/MultiThreadsCollider.java b/src/main/java/pl/amazingcode/threadscollider/multi/MultiThreadsCollider.java index 788cace..059fb79 100644 --- a/src/main/java/pl/amazingcode/threadscollider/multi/MultiThreadsCollider.java +++ b/src/main/java/pl/amazingcode/threadscollider/multi/MultiThreadsCollider.java @@ -6,6 +6,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -18,6 +19,14 @@ public final class MultiThreadsCollider implements AutoCloseable { private static final long DEFAULT_TIMEOUT = 60; private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS; + private static final ThreadFactory THREAD_FACTORY = + runnable -> { + Thread thread = new Thread(runnable); + thread.setDaemon(true); + thread.setName("collider-pool-" + thread.getName().toLowerCase()); + return thread; + }; + private final List runnables; private final List times; private final ExecutorService executor; @@ -40,14 +49,7 @@ private MultiThreadsCollider( this.runnables = runnables; this.times = times; this.threadsCount = threadsCount; - // ThreadFactory threadFactory = - // runnable -> { - // Thread thread = new Thread(runnable); - // thread.setDaemon(true); - // thread.setName("threads-collider-pool-" + thread.getName()); - // return thread; - // }; - this.executor = Executors.newFixedThreadPool(threadsCount); + this.executor = Executors.newFixedThreadPool(threadsCount, THREAD_FACTORY); this.spinLock = new AtomicBoolean(true); this.startedThreadsCount = new AtomicInteger(0); this.runningThreadsLatch = new CountDownLatch(threadsCount); diff --git a/src/test/java/pl/amazingcode/threadscollider/multi/Deadlock_Scenarios.java b/src/test/java/pl/amazingcode/threadscollider/multi/Deadlock_Scenarios.java new file mode 100644 index 0000000..a9cb90f --- /dev/null +++ b/src/test/java/pl/amazingcode/threadscollider/multi/Deadlock_Scenarios.java @@ -0,0 +1,69 @@ +package pl.amazingcode.threadscollider.multi; + +import static org.assertj.core.api.BDDAssertions.then; +import static pl.amazingcode.threadscollider.multi.MultiThreadsCollider.MultiThreadsColliderBuilder.multiThreadsCollider; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.RepeatedTest; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +final class Deadlock_Scenarios { + + private static final int ACTION_THREADS_COUNT = Runtime.getRuntime().availableProcessors() / 2; + private List list1; + private List list2; + + private void update1(List list1, List list2) { + + synchronized (list1) { + list1.add(1); + synchronized (list2) { + list2.add(1); + } + } + } + + private void update2(List list2, List list1) { + + synchronized (list2) { + list2.add(1); + synchronized (list1) { + list1.add(1); + } + } + } + + @BeforeEach + void setUp() { + list1 = new ArrayList<>(); + list2 = new ArrayList<>(); + } + + @RepeatedTest(10) + void Detect_deadlock() { + // Given + List exceptions = new ArrayList<>(); + + // When + try (MultiThreadsCollider collider = + multiThreadsCollider() + .withAction(() -> update1(list1, list2)) + .times(ACTION_THREADS_COUNT) + .withAction(() -> update2(list2, list1)) + .times(ACTION_THREADS_COUNT) + .withThreadsExceptionsConsumer(exceptions::add) + .withAwaitTerminationTimeout(100) + .asMilliseconds() + .build()) { + + collider.collide(); + } + + // Then + then(exceptions).isEmpty(); + } +}