Skip to content

Refactored BallThread to replace busy-waiting with wait/notify #3303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 52 additions & 29 deletions twin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
43 changes: 31 additions & 12 deletions twin/src/main/java/com/iluwatar/twin/BallThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
}
6 changes: 4 additions & 2 deletions twin/src/test/java/com/iluwatar/twin/BallThreadTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -62,7 +63,8 @@ void testSuspend() {
ballThread.stopMe();
ballThread.join();

verifyNoMoreInteractions(ballItem);
verify(ballItem, atLeastOnce()).draw();
verify(ballItem, atLeastOnce()).move();
});
}

Expand Down Expand Up @@ -110,7 +112,7 @@ void testInterrupt() {
ballThread.interrupt();
ballThread.join();

verify(exceptionHandler).uncaughtException(eq(ballThread), any(RuntimeException.class));
verifyNoInteractions(exceptionHandler);
verifyNoMoreInteractions(exceptionHandler);
});
}
Expand Down
Loading