Thread.start
Thread.start

Reputation: 13

Need help understanding memory visibility issues while Multithreading in java

I'm going through Goetze's Java Concurrency in practice and am stuck on the section on memory visibility of shared variables when not using the synchronized keyword.

The code is as follows

public class NoVisibility {

    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread {
        public void run() {
            while(!ready)
                Thread.yield();
            System.out.println(number);
        }
    }

    public static void main(String[] args){
        new ReaderThread().start();
        number=42;
        ready=true;
    }
}

The author says that this class could loop forever because the value of ready might never become visibile to the reader thread.

I don't understand this statement.

The way I see it is that first the main thread starts and sets the number and ready to true. But the other thread has its own stack and its own value of number and ready which is not synced with the main memory and these two threads have only their own copies of the variables.

Now the readerthread will supposedly keep in the loop forever. I want to know why the Thread.yield variable will not yield to the main thread and then the main thread should flush to the main memory and then the readerthread should pick up this new value and terminate the loop and print the correct value because that too should have been synched.

So I guess some questions I have are.

How often does the value in the cpu's cache get flushed/synced with main memory?

Can the value be not synced with the main memory is that also a possibility?

Why would this happen?

Does this memory visibility happen also when there is only once cpu core and one cpu cache or does it happen always?

I am having some trouble understanding the memory visibility problem though I understand race conditions and deadlocks. Is this something architecture specific?

Upvotes: 1

Views: 447

Answers (2)

SAIKAT
SAIKAT

Reputation: 1

Field visibility means that threads observer field values from the cache memory and can have different state from other cache in another core of the CPU. JVM does not guarantee field visibilty for different threads accessing shared resource and programmer needs to use sycchronized to prevent reading incorrect state or use volatile to guarantee the changes are flushed to other cache.

Upvotes: 0

Stephen C
Stephen C

Reputation: 719189

How often does the value in the cpu's cache get flushed/synced with main memory?

Undefined. Cache flushing happens when the visibility guarantees specified in the JLS say that it needs to happen.

Can the value be not synced with the main memory is that also a possibility?

Yes.

Why would this happen?

Generally speaking, caches get flushed for a reason. The happens-before relationships indicate the places where a cache flush may be necessary.

Does this memory visibility happen also when there is only once cpu core and one cpu cache or does it happen always?

If there is only one core, then cache flushing is not an issue1.

I am having some trouble understanding the memory visibility problem though I understand race conditions and deadlocks. Is this something architecture specific?

Yes and no. The memory visibility may manifest differently depending on the hardware architecture among other things, but the way to write your code to give well-defined behavior is architecture independent.

If you really need a deep understanding of the memory visibility problem, you need to understand the Memory Model. It is described in laymans terms in Goetz et al Chapter 16, and specified in the JLS.


I want to know why the Thread.yield() call will not yield to the main thread and then the main thread should flush to the main memory

  1. The Thread.yield() may yield to another runnable thread. However, by the time that yield() is called, it is quite likely that the main thread is no longer runnable. (Or it may still be running.)

  2. The yield() does not create a happens-before between any statements in the main and child threads. Absent that happens-before relation, the runtime is not obliged to ensure that the result of the assignment by the main thread is visible to the child thread.

  3. While Thread.yield() might perform a cache flush2, it would be a flush of the child thread's caches, not the parent thread's caches.

Hence, the child thread's loop may continue indefinitely.


1 - Actually, that may be an over-simplification. For example, in a system with one core and multiple hyperthreads with their own caches, cache flushing would be needed.

2 - For example, if the yield() does result in a context switch, then the context switch typically includes a cache flush as part of the thread state saving performed by the OS. However, yield() won't necessary result in a context switch. And besides, this aspect is not specified by the JLS.

Upvotes: 2

Related Questions