Search code examples
javamultithreadingvolatile

java volatile array,My test results do not match the expectations


According to the answer to this question(Java volatile array?), I did the following test:

public class Test {
    public static volatile long[] arr = new long[20];
    public static void main(String[] args) throws Exception {
        new Thread(new Thread(){
            @Override
            public void run() {
                //Thread A
                try {
                    TimeUnit.MILLISECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                arr[19] = 2;
            }
        }).start();
        new Thread(new Thread(){
            @Override
            public void run() {
                //Thread B
                while (arr[19] != 2) {
                }
                System.out.println("Jump out of the loop!");
            }
        }).start();
    }
}

As I know,for arrays objects,The volatile keyword only guarantees the visibility of the arr reference,no guarantee for elements in the array. However when Thread A changed the arr[19],Thread B found the change in arr[19] and Jump out of the loop.

So what is the problem?


Solution

  • Let me start off with a modification to your example:

    public class Test {
        public static long[] arr = new long[20]; // Make this non-volatile now
        public static volatile int vol = 0; // Make another volatile variable
    
        public static void main(String[] args) throws Exception {
            new Thread(new Thread(){
                @Override
                public void run() {
                    //Thread A
                    try {
                        TimeUnit.MILLISECONDS.sleep(1000);    
                        arr[19] = 2;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            new Thread(new Thread(){
                @Override
                public void run() {
                    //Thread B
                    while (true) {
                        int i = vol;
    
                        if (arr[19] == 2) {
                            break;
                        }
                    }
                    System.out.println("Jump out of the loop!");
                }
            }).start();
        }
    }
    

    You will realize that this will also cause Thread B to jump out of the loop (unless this symptom is something that is JIT-specific and mine happens to do this). The magic is this int i = vol; - or more precisely, the read of a volatile variable. Removing that line will cause Thread B to stay infinitely inside the loop.

    Therefore, it seems that any read to a volatile variable (i.e. any volatile reads) seem to retrieve the most updated values (including other non-volatile values).

    I tried to look into JLS but it is too complex for me to comprehend fully. I saw an article here that describes the visibility guarantee.

    From the article:

    If Thread A reads a volatile variable, then all all variables visible to Thread A when reading the volatile variable will also be re-read from main memory.

    (Ignore the typo of "all all" which is in the article.)

    In this case, it seems that all the data from the main memory will be updated back to the CPU cache when a volatile variable is read by the thread.


    Additional interesting findings: If you add another Thread.sleep() to Thread B (e.g. sleep for 50ms), the loop would manage to exit even without the read to the volatile variable. Surprising JLS 17.3 states this:

    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 Thread.sleep or Thread.yield, nor does the compiler have to reload values cached in registers after a call to Thread.sleep or Thread.yield.

    Again, I'm not sure if this symptom is JIT or JRE-specific again.