1

I have read the definition of volatile in java, And I run a small test project like below :

public class MainActivity extends AppCompatActivity
{

    static int num = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Thread readerThread = new Thread(() -> {
            int temp = 0;
            while (true)
            {
                if (temp != num) {
                    temp = num;
                    System.out.println("reader: value of num = " + num);
                }
            }
        });

        Thread writerThread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                num++;
                System.out.println("writer: changed value to = " + num);

                //sleep for readerThread to have enough time to read the change of num, since num++ is not atomic
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.exit(0);
        });

        readerThread.start();
        writerThread.start();

    }
}

if I run the main code on Eclipse, the log I get something like this:

writer: changed value to = 1
reader: value of num = 1
writer: changed value to = 2
writer: changed value to = 3
writer: changed value to = 4
writer: changed value to = 5

which is, I guess is right, the result means the reader did't get the value from main memory but the cache memory.

However, if I build the code on real android device by android studio, the log I get is:

I/System.out: reader: value of num = 1
I/System.out: writer: changed value to = 1
I/System.out: reader: value of num = 2
I/System.out: writer: changed value to = 2
I/System.out: writer: changed value to = 3
I/System.out: reader: value of num = 3
I/System.out: writer: changed value to = 4
I/System.out: reader: value of num = 4
I/System.out: writer: changed value to = 5
I/System.out: reader: value of num = 5

seems like without volatile the reader can still get the value from main memory, why the outcome is different between these two?

BobTheCat
  • 470
  • 4
  • 11
  • 3
    So ... if you *don't* use volatile, it is *unspecified* whether the reader will see the changes made by the writer. And you are running the code on different platforms where the what is happening in this *unspecified* case is different. This is just the kind of thing that happens when your application does things that are outside of the specified behavior of Java. – Stephen C Sep 16 '21 at 02:54
  • Why is it happen? There are any number of possible explanations, and it is not worth the effort to tie this down to a single one ... because you shouldn't write code like that (except as an experiment, of course). – Stephen C Sep 16 '21 at 02:59
  • Hi Stephen, thanks for comment. So you mean that at platform like android devices(android studio), without volatile declaration, there are (HIGHLY) chances for one thread see the changes made by other thread? – BobTheCat Sep 16 '21 at 03:05
  • That is your inference. It is not what I am actually saying though. – Stephen C Sep 16 '21 at 03:06
  • 1
    instead of figuring out a "test", figure out the specification and be assured that your code is going to work correctly. there are _many_ things that are wrong here, specifically the biggest one is `num++`, which you think is atomic, and atomicity has _nothing_ to do with `volatile`. – Eugene Sep 16 '21 at 03:13
  • @Eugene I do mention that num++is NOT atomic... – BobTheCat Sep 16 '21 at 03:17
  • 2
    my point is that `volatile` is about _happens-before_ edges, not "main memory". what `volatile` guarantees is in the specification. anything else is well, un-specified (captain obvious, I know). when something is un-specified - you are at risk, be that now, ten years after, on Android, or on M1. – Eugene Sep 16 '21 at 03:20
  • 1
    btw, you start your question with "I have read the definition of volatile in java...", but later you use words like "main memory" and use `num++`, etc. I think your initial source of inspiration was not good. there are many good article on SO about this, but I'll give you [this link](https://stackoverflow.com/questions/66162939/should-a-variable-be-volatile-between-2-running-threads/66163217#66163217) (of mine, yes) that should get you started – Eugene Sep 16 '21 at 03:44
  • Hi @Eugene and Stephen thanks a lot for helping me figure it out. So I think the conclusion is: anything within guarantee is guarantee , anything outside of it, in the case i have, could happened or not happened. Especially, in my case I test it on different platform which cause more variations. In other words, I can not prove volatile in opposite way. it's like A is B but B is A?(not sure, not guarantee ) – BobTheCat Sep 16 '21 at 04:15
  • 1
    sort of yes. where there is "data race", lots of ugly things can happen. – Eugene Sep 16 '21 at 04:18
  • @StephenC thanks a lot for helping me figure it out. – BobTheCat Sep 16 '21 at 04:21
  • If you want to play around with testing badly synchronized code, you might want to have a look at https://github.com/openjdk/jcstress. There are a ton of example tests. – pveentjer Sep 16 '21 at 07:38
  • so ahh, I come across this https://stackoverflow.com/a/47025817/16436336 and try it on eclipse(with & without volatile declaration) the difference of the results are understandable and simple. But to test it on android device(IDE: android studio), I can't tell the difference actually, I wonder what makes the outcome different from eclipse? Hardware? IO mechanism? since the language are the same: JAVA. – BobTheCat Sep 16 '21 at 08:41

1 Answers1

1

First of all I think you need to use JCStress framework to test everything related to Java concurrency. This will give you reliable results.

Second, when you write "the result means the reader did't get the value from main memory but the cache memory" you are stepping onto very thin ice, because JLS doesn't specify any particular behaviour in terms of main memory or caches, both are platform-dependent implementation details. JLS just specifies certain rules that must be followed by runtime. You might have an imaginary platform without caches in CPU at all, so it's pointless to get that deep on language level.

Third, I suppose when you run the code on Eclipse you have some x86 under the hood, and in case of Android it's probably ARM, so without proper synchronization it's hard to expect the same results.

Sergey Tsypanov
  • 3,265
  • 3
  • 8
  • 34
  • Even when ignoring the JMM, thinking in terms of flushing to main memory is flawed. Caches on modern processors are always coherent and force a write through to main memory would make concurrent applications extremely slow. – pveentjer Sep 23 '21 at 15:42