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

2

0

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?

Godsend answered 13/12, 2018 at 1:30 Comment(2)
Not an answer, just a guess, but you definitely did not let the JIT warm up that loop. Unless you had an on-stack replacement, the loop probably ran interpreted the whole time. Also, unrelated, but it's very odd that you're creating an anonymous subclass of Thread just to use it as a Runnable for another thread. You probably can drop the inner Thread and just call start() on the anonymous Thread subclass each time.Earthling
@DanielPryden I just did a test, did not consider the specification of the code.and does the volatile keyword guarantee the visibility of array elements? what is the correct answer?Godsend
S
1

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.

Sheriff answered 13/12, 2018 at 9:44 Comment(4)
Thanks for very nice Answer. I read the post,I think the code example can not support his conclusion,the field years months days stored in an adjacent memory space,They are on the same cache line,So the updating on volatile field days causing the invalidation of the cache line in Thread B. it's a different situation In my code instance. arr[19] and arr reference can not be on the same cache line.Additionally I don't know how the JIT-specific affect the test result.Godsend
@Godsend Well, I read your question (initially I misread it too), then found it interesting, so I went on to do some investigation. The JLS killed quite some of my brain cells, but I don't seem to see anything specific regarding how a JVM implementation should manage the synchronization between the CPU cache and the main memory.Sheriff
I believe what Jai is referring to is a happens-before relationship defined in the Java memory model. Refer this and thisPhototube
I find the answer which same with you at the book Java Concurrency in Practice 3.1.4Godsend
P
0

There are 2 things to be considered in multithreading.

  1. Compound statement atomicity (lock required to execute compound statements)
  2. Visibility(Inter thread visibility of values)

Your case lies in the latter one. Threads maintain a copy of values in their own space. Other threads may/may not see the value modified by one thread in its own copy. Thus you might not be able to replicate it.

Phototube answered 13/12, 2018 at 5:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.