From 70d5788077acd578064ad497ff0ec42ce6d5ee0d Mon Sep 17 00:00:00 2001 From: KANISHKMAKKAR <102965884+KANISHKMAKKAR@users.noreply.github.com> Date: Tue, 29 Sep 2026 15:54:04 +0530 Subject: [PATCH 1/7] fix: replace busy-wait in BallThread with wait/notify (#2977) --- .../java/com/iluwatar/twin/BallThread.java | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/twin/src/main/java/com/iluwatar/twin/BallThread.java b/twin/src/main/java/com/iluwatar/twin/BallThread.java index 7768d3ebbb99..b7b99b489ad0 100644 --- a/twin/src/main/java/com/iluwatar/twin/BallThread.java +++ b/twin/src/main/java/com/iluwatar/twin/BallThread.java @@ -36,6 +36,8 @@ public class BallThread extends Thread { @Setter private BallItem twin; + private final Object lock = new Object(); + private volatile boolean isSuspended; private volatile boolean isRunning = true; @@ -44,12 +46,18 @@ public class BallThread extends Thread { public void run() { while (isRunning) { - if (!isSuspended) { - twin.draw(); - twin.move(); - } try { - Thread.sleep(250); + synchronized (lock) { + // Wait to be notified instead of polling, so resuming takes effect immediately. + while (isSuspended && isRunning) { + lock.wait(); + } + } + if (isRunning) { + twin.draw(); + twin.move(); + Thread.sleep(250); + } } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -57,17 +65,25 @@ public void run() { } public void suspendMe() { - isSuspended = true; + synchronized (lock) { + isSuspended = true; + } LOGGER.info("Begin to suspend BallThread"); } public void resumeMe() { - isSuspended = false; + synchronized (lock) { + isSuspended = false; + lock.notifyAll(); + } LOGGER.info("Begin to resume BallThread"); } public void stopMe() { - this.isRunning = false; - this.isSuspended = true; + synchronized (lock) { + this.isRunning = false; + this.isSuspended = true; + lock.notifyAll(); + } } } From 7c10feca57f39c3493857bedb4c3437d72f62062 Mon Sep 17 00:00:00 2001 From: KANISHKMAKKAR <102965884+KANISHKMAKKAR@users.noreply.github.com> Date: Tue, 29 Sep 2026 15:54:11 +0530 Subject: [PATCH 2/7] docs: update twin README to match BallThread fix --- twin/README.md | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/twin/README.md b/twin/README.md index f6fb6923c069..d07f49d20a01 100644 --- a/twin/README.md +++ b/twin/README.md @@ -88,17 +88,24 @@ public class BallItem extends GameItem { public class BallThread extends Thread { @Setter private BallItem twin; + private final Object lock = new Object(); private volatile boolean isSuspended; private volatile boolean isRunning = true; public void run() { while (isRunning) { - if (!isSuspended) { - twin.draw(); - twin.move(); - } try { - Thread.sleep(250); + synchronized (lock) { + // Wait to be notified instead of polling, so resuming takes effect immediately. + while (isSuspended && isRunning) { + lock.wait(); + } + } + if (isRunning) { + twin.draw(); + twin.move(); + Thread.sleep(250); + } } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -106,18 +113,26 @@ public class BallThread extends Thread { } public void suspendMe() { - isSuspended = true; + synchronized (lock) { + isSuspended = true; + } LOGGER.info("Begin to suspend BallThread"); } public void resumeMe() { - isSuspended = false; + synchronized (lock) { + isSuspended = false; + lock.notifyAll(); + } LOGGER.info("Begin to resume BallThread"); } public void stopMe() { - this.isRunning = false; - this.isSuspended = true; + synchronized (lock) { + this.isRunning = false; + this.isSuspended = true; + lock.notifyAll(); + } } } ``` From 61b12ed5cf2823c71f0c1f4da215091fa5078a31 Mon Sep 17 00:00:00 2001 From: KANISHKMAKKAR <102965884+KANISHKMAKKAR@users.noreply.github.com> Date: Fri, 9 Oct 2026 13:06:39 +0530 Subject: [PATCH 3/7] Update BallThreadTest.java --- .../test/java/com/iluwatar/twin/BallThreadTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java index 6ad431ff649e..ad0335e5436d 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -114,4 +114,17 @@ void testInterrupt() { verifyNoMoreInteractions(exceptionHandler); }); } + + @Test + void testStopWhileSuspended() throws InterruptedException { + var t = new BallThread(); + var item = mock(BallItem.class); + t.setTwin(item); + t.suspendMe(); + t.start(); + sleep(300); + t.stopMe(); + t.join(); + verifyNoMoreInteractions(item); + } } From 8356b1e3c39c7529a5535d6cf5c27ff4eab0e2a2 Mon Sep 17 00:00:00 2001 From: KANISHKMAKKAR <102965884+KANISHKMAKKAR@users.noreply.github.com> Date: Fri, 9 Oct 2026 13:35:13 +0530 Subject: [PATCH 4/7] Update BallThreadTest.java --- .../com/iluwatar/twin/BallThreadTest.java | 59 +++++++++++++++---- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java index ad0335e5436d..adb348eb5664 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -115,16 +115,55 @@ void testInterrupt() { }); } + /** Verify if the BallThread can be stopped while suspended */ @Test - void testStopWhileSuspended() throws InterruptedException { - var t = new BallThread(); - var item = mock(BallItem.class); - t.setTwin(item); - t.suspendMe(); - t.start(); - sleep(300); - t.stopMe(); - t.join(); - verifyNoMoreInteractions(item); + void testStopWhileSuspended() { + assertTimeout( + ofMillis(5000), + () -> { + final var ballThread = new BallThread(); + + final var ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); + + ballThread.suspendMe(); + ballThread.start(); + + assertTimeout( + ofMillis(1000), + () -> { + while (ballThread.getState() != Thread.State.WAITING) { + Thread.yield(); + } + }); + + ballThread.stopMe(); + ballThread.join(); + + verifyNoMoreInteractions(ballItem); + }); + } + + /** Verify if the BallThread exits gracefully when interrupted while suspended */ + @Test + void testInterruptWhileSuspended() { + assertTimeout( + ofMillis(5000), + () -> { + final var ballThread = new BallThread(); + + final var ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); + + ballThread.suspendMe(); + ballThread.start(); + + sleep(200); + + ballThread.interrupt(); + ballThread.join(); + + verifyNoMoreInteractions(ballItem); + }); } } From a6cc3efd3400fe9948f98a8cb3b0b34eeffc1f28 Mon Sep 17 00:00:00 2001 From: KANISHKMAKKAR <102965884+KANISHKMAKKAR@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:08:07 +0530 Subject: [PATCH 5/7] Update BallThreadTest.java --- twin/src/test/java/com/iluwatar/twin/BallThreadTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java index adb348eb5664..155a718eb9d1 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -114,7 +114,6 @@ void testInterrupt() { verifyNoMoreInteractions(exceptionHandler); }); } - /** Verify if the BallThread can be stopped while suspended */ @Test void testStopWhileSuspended() { @@ -143,7 +142,6 @@ void testStopWhileSuspended() { verifyNoMoreInteractions(ballItem); }); } - /** Verify if the BallThread exits gracefully when interrupted while suspended */ @Test void testInterruptWhileSuspended() { From 568f395dce6a43aacadbda2dc385b8419a6f430d Mon Sep 17 00:00:00 2001 From: KANISHKMAKKAR <102965884+KANISHKMAKKAR@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:24:09 +0530 Subject: [PATCH 6/7] Update BallThreadTest.java --- .../com/iluwatar/twin/BallThreadTest.java | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java index 155a718eb9d1..52f9a1ca9cc9 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -114,54 +114,3 @@ void testInterrupt() { verifyNoMoreInteractions(exceptionHandler); }); } - /** Verify if the BallThread can be stopped while suspended */ - @Test - void testStopWhileSuspended() { - assertTimeout( - ofMillis(5000), - () -> { - final var ballThread = new BallThread(); - - final var ballItem = mock(BallItem.class); - ballThread.setTwin(ballItem); - - ballThread.suspendMe(); - ballThread.start(); - - assertTimeout( - ofMillis(1000), - () -> { - while (ballThread.getState() != Thread.State.WAITING) { - Thread.yield(); - } - }); - - ballThread.stopMe(); - ballThread.join(); - - verifyNoMoreInteractions(ballItem); - }); - } - /** Verify if the BallThread exits gracefully when interrupted while suspended */ - @Test - void testInterruptWhileSuspended() { - assertTimeout( - ofMillis(5000), - () -> { - final var ballThread = new BallThread(); - - final var ballItem = mock(BallItem.class); - ballThread.setTwin(ballItem); - - ballThread.suspendMe(); - ballThread.start(); - - sleep(200); - - ballThread.interrupt(); - ballThread.join(); - - verifyNoMoreInteractions(ballItem); - }); - } -} From 07e2e3c184e0f099049f6e82f5774fd29472101b Mon Sep 17 00:00:00 2001 From: KANISHKMAKKAR <102965884+KANISHKMAKKAR@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:25:52 +0530 Subject: [PATCH 7/7] Update BallThreadTest.java --- twin/src/test/java/com/iluwatar/twin/BallThreadTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java index 52f9a1ca9cc9..6ad431ff649e 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -114,3 +114,4 @@ void testInterrupt() { verifyNoMoreInteractions(exceptionHandler); }); } +}