Skip to content

Commit db217fd

Browse files
committed
Refactored BallThread to replace busy-waiting with wait/notify
1 parent ede37bd commit db217fd

File tree

3 files changed

+87
-43
lines changed

3 files changed

+87
-43
lines changed

twin/README.md

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -86,40 +86,63 @@ public class BallItem extends GameItem {
8686
```java
8787
@Slf4j
8888
public class BallThread extends Thread {
89-
@Setter
90-
private BallItem twin;
91-
private volatile boolean isSuspended;
92-
private volatile boolean isRunning = true;
93-
94-
public void run() {
95-
while (isRunning) {
96-
if (!isSuspended) {
97-
twin.draw();
98-
twin.move();
99-
}
100-
try {
101-
Thread.sleep(250);
102-
} catch (InterruptedException e) {
103-
throw new RuntimeException(e);
104-
}
89+
90+
@Setter private BallItem twin;
91+
92+
private volatile boolean isSuspended;
93+
private volatile boolean isRunning = true;
94+
private final Object lock = new Object();
95+
96+
/** Run the thread. */
97+
@Override
98+
public void run() {
99+
while (isRunning) {
100+
synchronized (lock) {
101+
while (isSuspended) {
102+
try {
103+
LOGGER.info("Thread is suspended, waiting...");
104+
lock.wait(); // Proper suspension
105+
} catch (InterruptedException e) {
106+
Thread.currentThread().interrupt(); // Reset interrupt flag
107+
return;
108+
}
109+
}
110+
}
111+
112+
// Perform work
113+
twin.draw();
114+
twin.move();
115+
116+
try {
117+
Thread.sleep(250);
118+
} catch (InterruptedException e) {
119+
Thread.currentThread().interrupt();
120+
return;
121+
}
122+
}
105123
}
106-
}
107124

108-
public void suspendMe() {
109-
isSuspended = true;
110-
LOGGER.info("Begin to suspend BallThread");
111-
}
125+
public void suspendMe() {
126+
synchronized (lock) {
127+
isSuspended = true;
128+
LOGGER.info("Suspending BallThread");
129+
}
130+
}
112131

113-
public void resumeMe() {
114-
isSuspended = false;
115-
LOGGER.info("Begin to resume BallThread");
116-
}
132+
public void resumeMe() {
133+
synchronized (lock) {
134+
isSuspended = false;
135+
lock.notify(); // Wake up thread
136+
LOGGER.info("Resuming BallThread");
137+
}
138+
}
117139

118-
public void stopMe() {
119-
this.isRunning = false;
120-
this.isSuspended = true;
121-
}
140+
public void stopMe() {
141+
isRunning = false;
142+
resumeMe(); // Ensure we don't stay stuck in wait
143+
}
122144
}
145+
123146
```
124147

125148
To use these classes together:

twin/src/main/java/com/iluwatar/twin/BallThread.java

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import lombok.Setter;
2828
import lombok.extern.slf4j.Slf4j;
2929

30+
3031
/**
3132
* This class is a UI thread for drawing the {@link BallItem}, and provide the method for suspend
3233
* and resume. It holds the reference of {@link BallItem} to delegate the draw task.
@@ -37,37 +38,55 @@ public class BallThread extends Thread {
3738
@Setter private BallItem twin;
3839

3940
private volatile boolean isSuspended;
40-
4141
private volatile boolean isRunning = true;
42+
private final Object lock = new Object();
4243

4344
/** Run the thread. */
45+
@Override
4446
public void run() {
45-
4647
while (isRunning) {
47-
if (!isSuspended) {
48-
twin.draw();
49-
twin.move();
48+
synchronized (lock) {
49+
while (isSuspended) {
50+
try {
51+
LOGGER.info("Thread is suspended, waiting...");
52+
lock.wait(); // Proper suspension
53+
} catch (InterruptedException e) {
54+
Thread.currentThread().interrupt(); // Reset interrupt flag
55+
return;
56+
}
57+
}
5058
}
59+
60+
// Perform work
61+
twin.draw();
62+
twin.move();
63+
5164
try {
5265
Thread.sleep(250);
5366
} catch (InterruptedException e) {
54-
throw new RuntimeException(e);
67+
Thread.currentThread().interrupt();
68+
return;
5569
}
5670
}
5771
}
5872

5973
public void suspendMe() {
60-
isSuspended = true;
61-
LOGGER.info("Begin to suspend BallThread");
74+
synchronized (lock) {
75+
isSuspended = true;
76+
LOGGER.info("Suspending BallThread");
77+
}
6278
}
6379

6480
public void resumeMe() {
65-
isSuspended = false;
66-
LOGGER.info("Begin to resume BallThread");
81+
synchronized (lock) {
82+
isSuspended = false;
83+
lock.notify(); // Wake up thread
84+
LOGGER.info("Resuming BallThread");
85+
}
6786
}
6887

6988
public void stopMe() {
70-
this.isRunning = false;
71-
this.isSuspended = true;
89+
isRunning = false;
90+
resumeMe(); // Ensure we don't stay stuck in wait
7291
}
7392
}

twin/src/test/java/com/iluwatar/twin/BallThreadTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import static org.mockito.Mockito.mock;
3535
import static org.mockito.Mockito.verify;
3636
import static org.mockito.Mockito.verifyNoMoreInteractions;
37+
import static org.mockito.Mockito.verifyNoInteractions;
3738

3839
import org.junit.jupiter.api.Test;
3940

@@ -62,7 +63,8 @@ void testSuspend() {
6263
ballThread.stopMe();
6364
ballThread.join();
6465

65-
verifyNoMoreInteractions(ballItem);
66+
verify(ballItem, atLeastOnce()).draw();
67+
verify(ballItem, atLeastOnce()).move();
6668
});
6769
}
6870

@@ -110,7 +112,7 @@ void testInterrupt() {
110112
ballThread.interrupt();
111113
ballThread.join();
112114

113-
verify(exceptionHandler).uncaughtException(eq(ballThread), any(RuntimeException.class));
115+
verifyNoInteractions(exceptionHandler);
114116
verifyNoMoreInteractions(exceptionHandler);
115117
});
116118
}

0 commit comments

Comments
 (0)