Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the relationship between Thread.sleep and happens-before?

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
                );
            }
        }
    }
}
like image 406
Ivan Ursul Avatar asked Feb 23 '17 13:02

Ivan Ursul


1 Answers

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.

like image 87
Chetan Kinger Avatar answered Sep 27 '22 21:09

Chetan Kinger