diff --git a/twin/README.md b/twin/README.md index f6fb6923c069..daa891d75916 100644 --- a/twin/README.md +++ b/twin/README.md @@ -86,40 +86,63 @@ public class BallItem extends GameItem { ```java @Slf4j public class BallThread extends Thread { - @Setter - private BallItem twin; - private volatile boolean isSuspended; - private volatile boolean isRunning = true; - - public void run() { - while (isRunning) { - if (!isSuspended) { - twin.draw(); - twin.move(); - } - try { - Thread.sleep(250); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + + @Setter private BallItem twin; + + private volatile boolean isSuspended; + private volatile boolean isRunning = true; + private final Object lock = new Object(); + + /** Run the thread. */ + @Override + public void run() { + while (isRunning) { + synchronized (lock) { + while (isSuspended) { + try { + LOGGER.info("Thread is suspended, waiting..."); + lock.wait(); // Proper suspension + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // Reset interrupt flag + return; + } + } + } + + // Perform work + twin.draw(); + twin.move(); + + try { + Thread.sleep(250); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } } - } - public void suspendMe() { - isSuspended = true; - LOGGER.info("Begin to suspend BallThread"); - } + public void suspendMe() { + synchronized (lock) { + isSuspended = true; + LOGGER.info("Suspending BallThread"); + } + } - public void resumeMe() { - isSuspended = false; - LOGGER.info("Begin to resume BallThread"); - } + public void resumeMe() { + synchronized (lock) { + isSuspended = false; + lock.notify(); // Wake up thread + LOGGER.info("Resuming BallThread"); + } + } - public void stopMe() { - this.isRunning = false; - this.isSuspended = true; - } + public void stopMe() { + isRunning = false; + resumeMe(); // Ensure we don't stay stuck in wait + } } + ``` To use these classes together: diff --git a/twin/src/main/java/com/iluwatar/twin/BallThread.java b/twin/src/main/java/com/iluwatar/twin/BallThread.java index 7768d3ebbb99..4a4f5858d941 100644 --- a/twin/src/main/java/com/iluwatar/twin/BallThread.java +++ b/twin/src/main/java/com/iluwatar/twin/BallThread.java @@ -27,6 +27,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; + /** * This class is a UI thread for drawing the {@link BallItem}, and provide the method for suspend * and resume. It holds the reference of {@link BallItem} to delegate the draw task. @@ -37,37 +38,55 @@ public class BallThread extends Thread { @Setter private BallItem twin; private volatile boolean isSuspended; - private volatile boolean isRunning = true; + private final Object lock = new Object(); /** Run the thread. */ + @Override public void run() { - while (isRunning) { - if (!isSuspended) { - twin.draw(); - twin.move(); + synchronized (lock) { + while (isSuspended) { + try { + LOGGER.info("Thread is suspended, waiting..."); + lock.wait(); // Proper suspension + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // Reset interrupt flag + return; + } + } } + + // Perform work + twin.draw(); + twin.move(); + try { Thread.sleep(250); } catch (InterruptedException e) { - throw new RuntimeException(e); + Thread.currentThread().interrupt(); + return; } } } public void suspendMe() { - isSuspended = true; - LOGGER.info("Begin to suspend BallThread"); + synchronized (lock) { + isSuspended = true; + LOGGER.info("Suspending BallThread"); + } } public void resumeMe() { - isSuspended = false; - LOGGER.info("Begin to resume BallThread"); + synchronized (lock) { + isSuspended = false; + lock.notify(); // Wake up thread + LOGGER.info("Resuming BallThread"); + } } public void stopMe() { - this.isRunning = false; - this.isSuspended = true; + isRunning = false; + resumeMe(); // Ensure we don't stay stuck in wait } } diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java index 6ad431ff649e..e3b238ed58ad 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyNoInteractions; import org.junit.jupiter.api.Test; @@ -62,7 +63,8 @@ void testSuspend() { ballThread.stopMe(); ballThread.join(); - verifyNoMoreInteractions(ballItem); + verify(ballItem, atLeastOnce()).draw(); + verify(ballItem, atLeastOnce()).move(); }); } @@ -110,7 +112,7 @@ void testInterrupt() { ballThread.interrupt(); ballThread.join(); - verify(exceptionHandler).uncaughtException(eq(ballThread), any(RuntimeException.class)); + verifyNoInteractions(exceptionHandler); verifyNoMoreInteractions(exceptionHandler); }); }