Understanding happens before and synchronization

I am trying to understand Java going on before ordering , and there are a few things that seem very confusing. As far as I can tell, earlier it was just an order for a set of actions and does not give any guarantees regarding the execution order in real time. Actually (emphasize mine):

It should be noted that the presence of a “happen-to” relationship between two actions does not necessarily mean that they must take place in this order in the implementation. If reordering creates results consistent with legal execution, this is not illegal.

So, all he says is that if there are two actions w (write) and r (read), so that hb (w, r) than r can actually happen before w is executed, but no guarantees that he will be. Also, the record w is observed while reading r .

How can I determine that two actions are performed subsequently at runtime? For example:

 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 this does not mean that c will contain the value 3 after assignment. How to ensure that c assigned with 3? Does the synchronization order provide such guarantees?

+7
java multithreading java-memory-model
source share
4 answers

When JLS says that some event X in thread A determines that the event occurs before the relationship with event Y in thread B, this does not mean that X will happen before Y.

This means that IF X occurs before Y, then both threads will agree that X happened before Y. That is, both threads will see program memory in a state that is consistent with X occurring before Y.


It is all about memory. Streams are transmitted through shared memory, but when there are several processors in the system, everyone is trying to access the same memory system, then the memory system becomes a bottleneck. Therefore, processors on a typical multiprocessor computer can delay, overwrite, and perform caching operations to speed things up.

This works great when threads don't interact with each other, but causes problems when they really want to interact. If thread A stores the value in a regular variable, Java does not guarantee when (or even if) thread B will see the value change.

To solve this problem when it is important, Java gives you certain means of thread synchronization. That is, so that the threads agree on the state of the program memory. The volatile keyword and the synchronized are two ways to establish synchronization between threads.


I think the reason they called it “happens earlier” is to emphasize the transitional 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 specified rules in JLS, you proved that A happens before C.

+8
source share

I would like to associate the above statement with some example code stream.

To understand this, take the class below, which 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 suppose there are three threads T1, T2, T3 that call the following methods on the same StateHolder object:

T1 calls resetCounter() and T2 calls printStateWithLock() at the same time, and T1 gets a lock
T3 -> calls printStateWithNoLock() after completion of T1

It should be noted that the presence of a relationship between two actions does not necessarily mean that they must be performed in this order in the implementation. If reordering leads to results that are consistent with legal execution, this is not illegal.

and the nearest line says

According to the statement above, this gives the flexibility of the JVM, OS, or underlying hardware to reorder operators in the resetCounter() method. And as T1 launches, it can follow the instructions in the following order.

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

This inline expression does not necessarily mean that they must be executed in that order in the implementation.

Now, looking at it from the point of view of T2, this reordering has no negative effect, since both T1 and T2 are synchronized on the same object, and T2 is guaranteed to see changes in both fields, regardless of whether the reordering has occurred or not, as it happens - before the relationship. Therefore, the conclusion will always be:

 Counter : 0 IsActive : true 

It’s like an operator. If reordering leads to results consistent with legal execution, it is not illegal

But look at it from the point of view of T3, while reordering that T3 will see the updated value of isActive as "true but still see the counter value as 100`, although T1 has completed its execution.

 Counter : 100 IsActive : true 

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

More specifically, if two actions share a wait relationship, they do not have to appear in that order for any code with which they do not share a wait relationship. Records in one thread that are in a data race with reading in another thread may, for example, appear out of order for these readings.

In this example, T3 ran into this problem because it has nothing to do - before the relationship with T1 or T2. It is inline with It does not have to appear in this order in any code with which they do not share the relationship between events.

NOTE. . To simplify the case, we have one state-changing thread T1, while T2 and T3 read the state. Maybe,

T1 counter to 0 updates, later
T2 changes isActive to true and sees counter is 0 , after some time T3, which prints the state, can still see only isActive as true but counter is 100 , although both T1 and T2 have completed execution.

Regarding the last question:

we have hb (w, r), but this does not mean that c will contain the value 3 after assignment. How to ensure c is assigned 3?

 public volatile int v; public int c; Thread A v = 3; //w Thread B c = v; //r 

Since v is volatile, how it is executed before ordering

Writes to an unstable field (§8.3.1.4) - before each subsequent reading of this field.

Therefore, we can safely 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.

+4
source share

Interpreting @James answer for my taste:

 // 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 processor cache) to be seen while the variable is being written by a single thread,

The memory state visible as hasValue=true (write statement) in Thread A:

first with a value of 5, second with a value of 6, third with a value of 7

it can be seen from each subsequent one (why it follows, although only read one in Thread B in this example? We really like Thread C like Thread B) read the statement of the same variable to another then mark this variable volatile .

If X ( hasValue=true ) in thread A occurs before Y ( sysout(hasValue) ) in thread B, the behavior should be as if X occurred before Y in the same thread (the memory values ​​seen with X should be the same, starting from Y)

+2
source share

Here we have hb (w, r), but this does not mean that c will contain the value 3 after assignment. How to ensure c is assigned 3? Does the synchronization order guarantee?

And your example

 public volatile int v; public int c; Actions: Thread A v = 3; //w Thread B c = v; //r 

You do not 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:

Theme A

 v = 3; assigned = true; 

Thread b

 while(!assigned); c = v; 
  • Field
  • assigned unstable.
  • We will have the c = v operator in Thread B only after assigned is true ( while(!assigned) is responsible for this).
  • if we have volatile we have happens before .
  • happens before means that if we see assigned == true , we will see everything that happened before the assigned = true operator: we will see v = 3 .
  • So, when we have assigned == true →, we have v = 3 .
  • As a result, we have c = 3 .

What happens without volatile

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

Actions:

Theme A

 v = 3; assigned = true; 

Thread b

 while(!assigned); c = v; 

Now assigned without volatile .

The value of c in Thread B may be 0 or 3 in this situation. Thus, there is no guarantee that c == 3 .

+1
source share

All Articles