1

I'm sorry if I'm doing a bad thing with this, but I have a question which is a spin-off of this question here:

Why volatile in java 5+ doesn't synchronize cached copies of variables with main memory?

Basically, I wanted to see what happends when the volatile is eliminated from the a variable. Here's the code from the original question, with my modification applied:

public class Test {
    //volatile static private int a;
    static private int a;
    static private int b;
public static void main(String [] args) throws Exception {
    for (int i = 0; i < 100; i++) {
        new Thread() {

            @Override
            public void run() {
                int tt = b; // makes the jvm cache the value of b

                while (a==0) {

                }
                //some threads never get here (past the a==0 loop)

                if (b == 0) {
                    System.out.println("error");
                }
            }

        }.start();
    }

    b = 1;
    a = 1;
}
}

And what happens on my laptop (Win 7 64, JVM build 1.7.0_04-b22) is that without the volatile, the code seems to run forever (left in running for 20 minutes). Adding some more console output told me that while most of the 100 threads do finally see the change in a from 0 to 1, there's always a few left (less than 10) that keep doing the a==0 loop.

My question is: will those threads finally see that change as well? If yes, is it normal to take 10s of thousands of times more time to do it as compared to the majority of similar threads? How come?

Community
  • 1
  • 1
Shivan Dragon
  • 15,004
  • 9
  • 62
  • 103
  • 3
    consider putting the code here as well in case the other question gets deleted, and just for the purposes of having self contained question. – Matti Lyra Aug 19 '13 at 11:02
  • Please post the code, people may not like browsing other pages – sanbhat Aug 19 '13 at 11:04
  • @MattiLyra: as per your request, I've added the code snippet. – Shivan Dragon Aug 19 '13 at 17:55
  • Thank you for the answers and comments, they're all very helpful. I see most people say in them that it's normal and somewhat expected to have the threads hang forever if volatile is not used (and I get now why this is so). However I was thrown off initially by some examples I've found which basically said that for such a case, the bad thing that can happen is that the "happens before" is not respected, i.e. some threads would see the change to a but not to b, not that some changes would never ever propagate. – Shivan Dragon Aug 19 '13 at 18:07

2 Answers2

7

This is not at all about the speed of the propagation of the variable; the machine code may be such that the memory write isn't even attempted. The value could reside in a register.

Anyway, my suggestion would be not to explore the behavior of broken Java code: it will always be different, anyway. Instead, learn how to write correctly synchronized programs. Study the Java Memory Model in detail and understand its guarantees. The guarantees of the JMM are quite distantly removed from the actual JVM implementation code which upholds those guarantees, and the JMM was specifically written to give the JVM the freedom to apply all kinds of optimizations.

As a specifically poignant example, the behavior of your broken code can change in the midst of its execution, as the interpreted, or C1 code is being replaced just-in-time with C2 code. There is just nothing to learn from running the program you refer to.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
4

Each thread has its own local memory. Making changes, even to the same variable, from one thread, may not (and you should assume: will not) propagate to other threads.

To enforce memory synchronization between threads you have to use synchronized block and obtain the variable value within that block.

The simplest way to achieve that is to use Atomic-class variable, like AtomicInteger. It ensures atomic and data race-free access to their values.

Using volatile is an option, but it prevents any compiler optimization of that variable. Besides, it only applies to atomic operations, you may encounter various race conditions with it, which can be avoided using a synchronized block.

The most precise and detailed information on how Java Memory model actually works can be found in the Java Language Specification and JSR-133 Java Memory Model and Thread Specification. Various interpretations (including mine) of these documents may be wrong, so remember to look at reliable sources when in doubt.

Dariusz
  • 21,561
  • 9
  • 74
  • 114
  • Not *data races*, which is an official JMM term. *Race conditions* of other kinds, yes. – Marko Topolnik Aug 19 '13 at 11:20
  • `AtomicInteger` is all about *lock-free programming*. Grep its source code for `synchronized`, you won't find it. – Marko Topolnik Aug 19 '13 at 11:21
  • @MarkoTopolnik ack, Atomics use volatiles; as for data races: I will trust you on this and will read [Formalising Java’s Data Race Free Guarantee](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.63.7338&rep=rep1&type=pdf) later. – Dariusz Aug 19 '13 at 11:24
  • My suggestion is [the horse's mouth](http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5). – Marko Topolnik Aug 19 '13 at 11:27
  • 1
    Atomics also use the `Unsafe` class to do low-level compare-and-swap operations, which often map to single CPU instructions. Therein lies their true magic. – Marko Topolnik Aug 19 '13 at 11:36
  • Thanks for you answer. I have a follow up which might seem dumb but oh well: you say that adding "synchronized" would propagate the change of the "a" variable throughout all threads, just as adding volatile to it would? i.e.: if i surrounded the code changing the variable to 1 and the code doing the checking in the loop with synchronized (and lock on the same object each time) I'd properly get the variable change propagated? – Shivan Dragon Aug 19 '13 at 18:03
  • @ShivanDragon: in short, yes. But synchronized block ensures complete memory refresh, which means that all variables accessed in a synchronized block will have their state updated to their state at the beginning of the synchronized block. That way a synchronized block is more powerful. And, of course, locking on the same object prevents concurrent execution of that critical section. – Dariusz Aug 19 '13 at 19:27
  • Again, this is a wrong claim: only the writes of another thread preceding that thread's release of the lock you are acquiring in the current thread are guaranteed to be visible. This is nowhere near the stated full memory refresh. – Marko Topolnik Aug 19 '13 at 20:31
  • And the synchronized block is not an inch more powerful in this respect than a volatile, they are exactly the same. Please get to know the JMM, it is your responsibility not to spread misinformation – Marko Topolnik Aug 19 '13 at 20:34
  • @MarkoTopolnik [JSR-133 FAQ](http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html#synchronization) *Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory. We will then be able to see all of the writes made visible by the previous release.* – Dariusz Aug 20 '13 at 05:08
  • Java Language Specification specifies no processor caches or anything remotely similar. Instead of acquainting yourself with that, you read secondary sources, which explain rather than specify, by dumbing down the full picture. As a result you answer questions about Java in general with information which may happen to be true on some implementations, and will be completely off on others. But more tragically, you are making people develop incorrect mental models of the JMM. If you were familiar with the JMM rather than with what people talk about it, you wouldn't be giving such answers. – Marko Topolnik Aug 20 '13 at 06:27
  • @MarkoTopolnik I do not understand your attitude. I have posted a link to an article written by authors of JSR 133, which (as far as I understand it) is the standard for JVMs. Apart from your opinions, with which I actually agree, you have posted no proof that what I am saying is wrong. – Dariusz Aug 20 '13 at 06:51
  • 1) I have posted proof: it's the JMM. And yes, the authors of the JSR 133 *are*, unlike you, entitled to expertly dumb down the model which they deeply understand. You, however, only possess thet dumbed-down picture, from which you generalize into completely wrong conclusions. – Marko Topolnik Aug 20 '13 at 06:57
  • 2) Specifically, if Thread `A` releases lock `a` and Thread `B` acquires lock `b`, as far as the JMM is concerned, this is exactly the same as no threads acquiring any locks. This is completely opposite of what you have said above. You have also implied that `volatile`s do not share the same visibility semantics as locks, which is another flat-out falsehood. – Marko Topolnik Aug 20 '13 at 06:58
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/35816/discussion-between-dariusz-and-marko-topolnik) – Dariusz Aug 20 '13 at 06:58
  • Allrighty:) Thank you very much, I've tried the synchronized version of code as well (I've modified the loop of course, otherwise I'd just get in a very cool deadlock:) ) and it works just as you guys said. This has really helped clearing up how this works. Thank you. – Shivan Dragon Aug 20 '13 at 07:10
  • 1
    @ShivanDragon I have added important links to my answer. Regardless of whether in your case what I wrote is true or not, these links I added are the best sources of information. Good luck! – Dariusz Aug 20 '13 at 07:51