Understanding happens-before and synchronization [duplicate]

三世轮回 提交于 2019-12-17 19:20:16

问题


I'm trying to understand Java happens-before order concept and there are a few things that seem very confusing. As far as I can tell, happens before is just an order on the set of actions and does not provide any guarantees about real-time execution order. Actually (emphasize mine):

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

So, all it says is that if there are two actions w (write) and r (read) such that hb(w, r), than r might actually happens before w in an execution, but there's no guarantee that it will. Also the write w is observed by the read r.

How I can determine that two actions are performed subsequently in run-time? For instance:

public volatile int v;
public int c;

Actions:

Thread A
v = 3;  //w

Thread B
c = v;  //r

Here we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3? Does synchronization order provide such guarantees?


回答1:


When the JLS says that some event X in thread A establishes a happens before relationship with event Y in thread B, it does not mean that X will happen before Y.

It means that IF X happens before Y, then both threads will agree that X happened before Y. That is to say, both threads will see the program's memory in a state that is consistent with X happening before Y.


It's all about memory. Threads communicate through shared memory, but when there are multiple CPUs in a system, all trying to access the same memory system, then the memory system becomes a bottleneck. Therefore, the CPUs in a typical multi-CPU computer are allowed to delay, re-order, and cache memory operations in order to speed things up.

That works great when threads are not interacting with one another, but it causes problems when they actually do want to interact: If thread A stores a value into an ordinary variable, Java makes no guarantee about when (or even if) thread B will see the value change.

In order to overcome that problem when it's important, Java gives you certain means of synchronizing threads. That is, getting the threads to agree on the state of the program's memory. The volatile keyword and the synchronized keyword are two means of establishing synchronization between threads.


I think the reason they called it "happens before" is to emphasize the transitive nature of the relationship: If you can prove that A happens before B, and you can prove that B happens before C, then according to the rules specified in the JLS, you have proved that A happens before C.




回答2:


I would like to associate the above statement with some sample code flow.

To understand this, let us take below class that has two fields counter and isActive.

class StateHolder {
    private int counter = 100;
    private boolean isActive = false;

    public synchronized void resetCounter() {
            counter = 0;
            isActive = true;
    }

    public synchronized void printStateWithLock() {
        System.out.println("Counter : " + counter);
        System.out.println("IsActive : " + isActive);
    }

    public void printStateWithNoLock() {
        System.out.println("Counter : " + counter);
        System.out.println("IsActive : " + isActive);
    }
}

And assume that there are three thread T1, T2, T3 calling the following methods on the same object of StateHolder:

T1 calls resetCounter() and T2 calls printStateWithLock() at a same time and T1 gets the lock
T3 -> calls printStateWithNoLock() after T1 has completed its execution

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

and the immediate line says,

As per the above statement, it gives the flexibility for JVM, OS or underlying hardware to reorder the statements within the resetCounter() method. And as T1 gets executed it could execute the statements in the below order.

    public synchronized void resetCounter() {
            isActive = true;
            counter = 0;
    }

This is inline with the statement not necessarily imply that they have to take place in that order in an implementation.

Now looking at it from a T2 perspective, this reordering doesn't have any negative impact, because both T1 and T2 are synchronizing on the same object and T2 is guaranteed to see changes changes to both of the fields, irrespective of whether the reordering has happened or not, as there is happens-before relationship. So output will always be:

Counter : 0
IsActive : true

This is as per statement, If the reordering produces results consistent with a legal execution, it is not illegal

But look at it from a T3 perspective, with this reordering it possible that T3 will see the updated value of isActive as 'truebut still see thecountervalue as100`, although T1 has completed its execution.

Counter : 100
IsActive : true

The next point in the above link further clarifies the statement and says that:

More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.

In this example T3 has encountered this problem as it doesn't have any happens-before relationship with T1 or T2. This is inline with Not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship.

NOTE: To simplify the case, we have single thread T1 modifying the state and T2 and T3 reading the state. It is possible to have

T1 updates counter to 0, later
T2 modifies isActive to true and sees counter is 0, after sometime
T3 that prints the state could still see only isActive as true but counter is 100, although both T1 and T2 have completed the execution.

As to the last question:

we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3?

public volatile int v;
public int c;

Thread A
v = 3;  //w

Thread B
c = v;  //r

Since v is a volatile, as per Happens-before Order

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

So it is safe to assume that when Thread B tries to read the variable v it will always read the updated value and c will be assigned 3 in the above code.




回答3:


Interpreting @James' answer to my liking:

// Definition: Some variables
private int first = 1;
private int second = 2;
private int third = 3;
private volatile boolean hasValue = false;

// Thread A
first = 5;
second = 6;
third = 7;
hasValue = true;

// Thread B
System.out.println("Flag is set to : " + hasValue);
System.out.println("First: " + first);  // will print 5
System.out.println("Second: " + second); // will print 6
System.out.println("Third: " + third);  // will print 7

if you want the state/value of the memory(memory and CPU cache) seen at the time of a write statement of a variable by one thread,

State of the memory seen by hasValue=true(write statement) in Thread A :

first having value 5,second having value 6,third having value 7

to be seen from every subsequent(why subsequent even though only one read in Thread B in this example? We many have Thread C doing exactly similar to Thread B) read statement of the same variable by another thread,then mark that variable volatile.

If X (hasValue=true) in Thread A happens before Y (sysout(hasValue)) in Thread B, the behaviour should be as if X happened before Y in the same thread (memory values seen at X should be same starting from Y)




回答4:


Here we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3? Does synchronization order provide such guarantees?

And your example

public volatile int v;
public int c;
Actions:

Thread A
v = 3;  //w

Thread B
c = v;  //r

You don't need volatile for v in your example. Let's take a look at a similar example

int v = 0;
int c = 0;
volatile boolean assigned = false;

Actions:

Thread A

v = 3;
assigned = true;

Thread B

while(!assigned);
c = v;
  1. assigned field is volatile.
  2. We will have c = v statement in Thread B only after assigned will be true (while(!assigned) is responsible for that).
  3. if we have volatile — we have happens before.
  4. happens before means that, if we see assigned == true — we will see all that happened before a statement assigned = true: we will see v = 3.
  5. So when we have assigned == true -> we have v = 3.
  6. We have c = 3 as a result.

What will happen without volatile

int v = 0;
int c = 0;
boolean assigned = false;

Actions:

Thread A

v = 3;
assigned = true;

Thread B

while(!assigned);
c = v;

We have assigned without volatile for now.

The value of c in the Thread B can be equal 0 or 3 in such situation. So there is not any guaranties that c == 3.



来源:https://stackoverflow.com/questions/36383542/understanding-happens-before-and-synchronization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!