1
votes

There is such a saying in this question:

All caches are coherent. That is, you will never had two different values for the same memory location. This coherency is maintained by a version of the MESI protocol

My question is why iv.stop is always false in the block of code below, it seems that cache coherency doesn't take effect. BTW, my PC's CPU is i7-4700HQ.

I definitely know right here there is no happens-before relationship between the read action and the write action of the shared variable stop and this is a data race in Java. I just want to know why cache coherency doesn't take effect. Because thread t2 has changed the cache of stop in its running core, it seems that the core's cache should see this change where thread t1 is running according to cache coherency.

public class InfiniteLoop {
    boolean stop = false;

    Boolean another = null;

    public static void main(String[] args) {

        final InfiniteLoop iv = new InfiniteLoop();

        Thread t1 = new Thread(() -> {
            System.out.println("t1 iv address-->"+iv); //t1 iv address-->com.nestvision.thread.InfiniteLoop@48b96cb8
            while (!iv.stop) {
                //Not System.out.println(iv.stop) here to avoid 
                //a lock action of PrintStream object.
                iv.another = iv.stop;
            }
            System.out.println("done");
        });

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 iv address-->"+iv);//t2 iv address-->com.nestvision.thread.InfiniteLoop@48b96cb8
            iv.stop = true;
            System.out.println("t2 end");
        });
        t2.start();
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(iv.another); //this will print 'false'
    }
}
1
When programming in Java, you should probably read about Java synchronization and the Java memory model, not about low-level CPU stuff.user140547
Implement an atomic getter and setter for iv, and give it a go.Isuru H
Could you also try to print the memory address (object address within JVM) of iv inside both threads.Isuru H
@IsuruH getter and setter act the same behavior. I printed the address of iv in each thread you'd see in the code above, they're the same object as prediction.XiangZzz
@XiangZzz thanks for the addresss, but I do not see any atomic getters and setters for iv in the code.Isuru H

1 Answers

2
votes

First, your print at the end will could print before the threads start and definitely before they both finish. You probably want to use some of the java concurrency classes to block the main thread until the other threads finish although that will probably infinite loop and wait forever until you do the fix below.

Another problem is your stop variable is not volatile so different threads may not see the value.

This other answer has information regarding boolean values and L1/L2 caches across processors How is memory inconsistency different from thread interleaving?

Relevant quote:

Thread A reads the boolean value: it will necessarily read false (see the previous bullet point). When this read happens, it might happen that some memory pages, including the one containing that boolean, will be cached into Core 1's L1 cache, or L2 cache -- any cache local to that specific core.

Thread A negates and stores the boolean value: it will store true now. But the question is: where? Until a happens-before occur, Thread A is free to store this new value only on the local cache to the Core running the thread. Therefore, it is possible that the value will be updated on Core 1's L1/L2 cache, but will remain unchanged in the processor's L3 cache or in RAM.

After some time (according to your wall clock), Thread B reads the boolean value: if Thread A didn't flush the changes into L3 or RAM, it's entirely possible that Thread B will read false. If Thread A, on the other hand, had flushed the changes, it is possible that Thread B will read true (but still not guaranteed -- Thread B might have received a copy of Thread M's view of the memory and, due to the lack of happens-before, it won't go to the RAM again and will still see the original value).

The only way to guarantee anything is to have an explicit happens-before: it would force Thread A to flush its memory, and would force Thread B to read not from a local cache, but truly read it from the "authoritative" source.

Without a happens-before, as you can see form the example above, anything can happen, no matter how much time (from your perspective) elapses between events in different threads.

...

If that boolean variable is marked as volatile...then, only then, Thread B is guaranteed to see true (ie, otherwise there are no guarantees at all).

The reason is that volatile helps establish happens-before relationships. It goes as follows: a write to a volatile variable happens-before any subsequent read to the same variable.