I've written a simple application, which has main thread(producer) and multiple consumer threads. I want to broadcast a message from main thread, so all the consumer threads will receive it. However, I have troubles. I'm trying to understand how Thread.sleep may be related to Happens-Before. Here's my piece of code:
import java.util.*;
public class PubSub {
public static void main(String[] args) {
List<Consumer> consumers = new ArrayList<>();
for (int i = 0; i < 3; i++ ) {
Consumer consumer = new Consumer(
"Consumer" + i
);
consumer.start();
consumers.add(consumer);
}
Scanner scanner = new Scanner(System.in);
while(true) {
String message = scanner.nextLine();
for (Consumer consumer: consumers) {
consumer.notify(message);
}
}
}
static class Consumer extends Thread {
private Queue<String> queue;
public Consumer(String name) {
super(name);
this.queue = new LinkedList<>();
}
@Override
public void run() {
while(true) {
if (!queue.isEmpty()) {
String message = queue.poll();
System.out.println(
getName() + ": Consuming message: " + message
);
}
}
}
public void notify(String message) {
queue.add(message);
}
}
}
If I add sleep, the consumer will start receiving messages:
@Override
public void run() {
while(true) {
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!queue.isEmpty()) {
String message = queue.poll();
System.out.println(
getName() + ": Consuming message: " + message
);
}
}
}
Since Thread.sleep is a native method, I want to understand how it resolves happens-before.
I must note, that the true way of fixing happens-before is to make volatile keyword:
private volatile Queue<String> queue;
If you add synchronization onto the queue, it will also fix the issue:
@Override
public void run() {
synchronized (queue) {
while (true) {
if (!queue.isEmpty()) {
String message = queue.poll();
System.out.println(
getName() + ": Consuming message: " + message
);
}
}
}
}
I'm trying to understand how Thread.sleep may be related to Happens-Before
There is no relationship between happens-before and Thread.sleep
. The JLS clearly specifies that Thread.sleep
does not have any synchronization symantics :
Neither a sleep for a period of zero time nor a yield operation need have observable effects. It is important to note that neither Thread.sleep nor Thread.yield have any synchronization semantics. In particular, the compiler does not have to flush writes cached in registers out to shared memory before a call to sleep or yield, nor does the compiler have to reload values cached in registers after a call to sleep or yield. For example, in the following (broken) code fragment, assume that this.done is a non-volatile boolean field:
while (!this.done) Thread.sleep(1000);
The compiler is free to read the field this.done just once, and reuse the cached value in each execution of the loop. This would mean that the loop would never terminate, even if another thread changed the value of this.done
On a related note, the consumer seems to start consuming messages (on my machine) if one adds statements that are evaluated at runtime right after the while
loop. For example, the addition of this.toString()
causes the consumer to start consuming messages.
public void run() {
while(true) {
this.toString();
This still does not explain the behavior but it further confirms that Thread.sleep
is not necessarily the reason behind the Consumer
suddenly seeing the updates made to the queue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With