3

Should int a in this case be volatile to guarantee visibilty between threads?

private volatile static int a = 0;

public static void main(String[] args) {
    
    Thread t1 = new Thread(new Runnable() {
        
        @Override
        public void run() {
            a = 10;
        }
    });

    
    Thread t2 = new Thread(new Runnable() {
        
        @Override
        public void run() {
            System.out.println(a);
        }
    });
    
    t1.start();
    t2.start();
    
}

Output

10
duh ikal
  • 161
  • 1
  • 8
  • 4
    1) do you know which thread starts first? (hint: you don't) 2) `volatile` is about two entities - when some write is _seen_ by some other thread, it means the `happens-before` is established... – Eugene Feb 11 '21 at 21:14
  • Could you explain yourself a bit further on "happens-before"? And you didn't really answer if in this case the volatile keyword is needed. – duh ikal Feb 11 '21 at 21:20
  • The problem is that this level it is very difficult to know the basics and apply it correctly. But in this case making a volatile would do the trick (it establishes the happens before relation as Eugene already pointed out). – pveentjer Feb 12 '21 at 05:02

2 Answers2

5

happens-before is clearly defined in the language specification, start by reading that; to begin with.

Then to fully understand what is going on you need to know what Program order is, as well as synchronization order.

To put it very simplified, look at the below:

private volatile static int a = 0;
private static int b = 0;

public static void main(String[] args) {

    Thread t1 = new Thread(new Runnable() {

        @Override
        public void run() {
            b = 100;
            a = 10;
        }
    });


    Thread t2 = new Thread(new Runnable() {

        @Override
        public void run() {
            if(a == 10){
                System.out.println(b);
            }

        }
    });

    t1.start();
    t2.start();

}

The only guarantee you have is that if, and only if, t2 prints something, it will always be 100. This is because t2 has seen a volatile write to a. That happens because a "happens-before" has been established, from the writing thread to the reading one, and every action done before a = 10 is guaranteed to be visible to the thread that has seen that a being 10.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Because hb(b=100, a=10) because they're actions in the same thread in that program order (rule 1), and `a == 10` implies hb(a=10, t2), which means hb(b=100, t2) from rule 4 - right? – daniu Feb 11 '21 at 21:43
  • @daniu yes, but of course you need to stay in proper bounds of the `JLS`. [here](https://stackoverflow.com/questions/65527237/java-memory-model-a-jls-statement-about-sequential-consistency-seems-incorrect/65546051#65546051) and [here](https://stackoverflow.com/a/65016214/1059372) is where I only _scrapped_ the surface of it. But really had fun. – Eugene Feb 11 '21 at 21:50
3

Could you explain yourself a bit further on "happens-before"?

The most important thing to remember about "happens before" is that it's a transitive relation. That means, if the Java Language Spec (JLS) promises that A "happens before" B, and it promises that B "happens before" C, then you can infer a promise that A "happens before" C.

The JLS says that a write to some volatile variable "happens before" a subsequent read of the same variable.

Well Duh! Sounds obvious, doesn't it?

But it's not obvious because the JLS does not give the same guarantee for a non-volatile variable. If processor A writes the value 7 to a non-volatile int, and then some time later processor B writes 5, the JLS does not guarantee that some long time later, the final value of the variable will be 5. Processor B will see 5 (that's a different "happens before" promise, see below). Processor A could see 5 or 7, and any other processor could see 5 or 7 or whatever value the variable had initially (e.g., 0).

How the volatile promise helps

Suppose we have

 volatile boolean flag = false;
 /*non-volatile*/ int i = 0;

Suppose thread A does this:

i = 7;
flag = true;

And suppose thread B does this:

if (flag) {
    System.out.println(i);
}
else {
    System.out.println("Bleah!");
}

Thread B could print "7", or it could print "Bleah!" but because of the "happens before" guarantee, we absolutely know that thread B will never print "0". Why not?

Thread A set i = 7 before it set flag = true. The JLS guarantees that if a single thread executes one statement before it executes a second statement, then the first statment "happens before" the second statement. (That sounds stupendously obvious, but again, it shouldn't. A lot of things having to do with threads are not obvious.)

Thread B tests flag before it prints i. So *IF* thread A previously set flag=true then we know that i must equal 7: Transitivity: i=7 "happens before" flag=true, and the write to volatile flag, IF IT HAPPENED AT ALL, "happens before" a read of the same flag.

IF IT HAPPENED AT ALL

Data Races and Race Conditions

The biggest thing to remember is that when the JLS promises that A "happens before" B, they are not saying that A always actually does happen before B: They are saying that you can depend on that transitive relationship. They are saying that if A actually did happen before B, then all of the things that "happens before" A must also have actually happened before B.

The program can print "Bleah!" because nothing prevents thread B from testing the flag before thread A sets it. Some people call that a "data race." The two threads are "racing" to see which one gets to the flag first, and the outcome of the program depends on which thread wins that race.

When the correctness of a program depends on the outcome of a data race, some of us call that a "race condition," and that's a real headache. There's no guarantee that a program with a race condition won't do the right thing a thousand times during your testing, and then do the wrong thing when it matters most for your customer.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57