610 likes | 916 Views
The Java Memory Model. JMM: SC intuition may fail. Application programmer supposes sequential consistent memory model The trace proves that the memory model is not sequential consistent (cycle of program order + value inheritance) The execution doesn’t violate the Java Memory Model.
E N D
JMM: SC intuition may fail • Application programmer supposes sequential consistent memory model • The trace proves that the memory model is not sequential consistent (cycle of program order + value inheritance) • The execution doesn’t violate the Java Memory Model
Operations defined in JMM • Abstractions of Java bytecodes • Concern to heap accesses • There are synchronization memory accesses • Locking monitor (while entering synchronized method or block), reading volatile variable (in the new JMM) and calling a start() on a thread works as acquire. • Unlocking monitor (while leaving synchronized method or block), writing volatile variable (in the new JMM) and calling a join() on a thread works as release.
The old (operational) JMM • Constrains inside a thread • Constrains between a thread and the main memory • General constrains • Locks • Volatile variables • Prescient stores Execution engine thread use assign Working memory for each thread lock unlock local copies load store write read Main memory master copies
The JMM is changing • Problems of the old JMM: • Prohibits common compiler optimizations • Common programming idioms are not guaranteed to work (the double check idiom as an example) • It is hard for understanding • given in the operational form • The new JMM and the old code: • Correctly synchronized code would work OK • The code which is not correctly synchronized remains broken
// Broken multithreaded version // "Double-Checked Locking" idiom class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) synchronized(this) { if (helper == null) helper = new Helper(); } return helper; } // other functions and members... } Used for implementing lazy initialization May go wrong if the memory model is weaker than Sequential Consistency a thread which invokes getHelper() could see a non-null reference to a helper object, but see the default values for fields of the helper object, rather than the values set in the constructor There is no way to fix it for the common case Failed on the old JMM The double check idiom* * By http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
The double check idiom: how to solve the problem in Java? • Use permanent synchronization • Common method not only for Java • Use static singletons • The semantics of Java guarantee that the static field will not be initialized until the field is referenced, and that any thread which accesses the field will see all of the writes resulting from initializing that field. • Double checking will work for 32-bit primitive values • Although the double-checked locking idiom cannot be used for references to objects, it can work for 32-bit primitive values
The double check idiom: how to solve the problem in Java? (continue) • Use Thread Local Storage (the solution of Alexander Terekhov ) • Each thread keeps a thread local flag to determine whether that thread has done the required synchronization. • The new JMM makes it possible to fix the double-checked locking using volatile • Declare the helper field to be volatile • Is effective if volatile synchronization is cheaper than the explicit synchronization (by synchronized) • Use final fields • Make helper immutable
Requirements to the new JMM(by S. Adve) • SC for data-race-free programs • Safety / security for programs with data races • Optimizations must be allowed • Understandability
Features of the new JMM • Total order of synchronization actions, consistent with program order • Happens – before consistency (see below) • For each execution it must be possible to (virtually) commit all actions in some total order
Why formal JMM definition ? • JVMs produced by different vendors must be formally estimated correct / incorrect • Automatic verification needs formal model
The project of the new JMM • The new JMM would go into operation in Tiger (1.5) release of Java • The discussion continues, but the main ideas are just advertised • The current status: two concurrent groups of experts – (1) Manson and Pugh (University of Maryland) and (2) Adve (University of Illinois at Urbana-Champaign) has come to the agreement and proposed their “unified” model
The new JMM: happens-before order • SW (synchronized with) order – from release action, preceding in the total synchronization order, to subsequent acquire action on the same monitor / volatile variable • HB = SW U PO
Roach motel • It is generally legal to reorder a normal memory access and a following acquire action, or a release action and a following normal memory access. This has been referred to as “roach motel” semantics: variable accesses can be moved into a synchronization block, but cannot, in general, move out
Useless synchronization may be removed Initially, A == B ==0 • Lock acquisition on thread-local objects • Reacquisition of a lock on an object on which a thread already has a lock • Other cases detected by compiler analyses May observe r3 = 1 and r4 = 0
Happens-before order: SW • An unlock action on monitor m synchronizes-with all subsequent (in synchronization order) lock actions on m • A write to volatile variable v synchronizes-with all subsequent reads of v by any thread • An action that starts a thread synchronizes-with the first action in the thread it starts • The final action in a thread T1 synchronizes-with any action in another thread T2 that detects that T1 has terminated (isAlive, join)
Synchronizes-with (continue) • If thread T1 interrupts thread T2, the interrupt by T1 synchronizes-with any point where any other thread (including T2) determines that T2 has been interrupted • The write of the default value (zero, false or null) to each variable synchronizes-with the first action in every thread • There is a happens-before edge from the end of a constructor of an object to the start of a finalizer for that object
Happens-before consistency • Read must not precede by happens-before to the Write whose value it reads • Between Write W and its corresponding Read R may not be other write W’ to the same variable such that their order by happens-before: W → W’ → R • Problem: happens-before consistency doesn’t provide SC for correctly synchronized programs • Problem: happens-before consistency doesn’t provide safety guarantees
Correctly synchronized program • The program is correctly synchronized if all possible SC executions of the program haven’t data races • JMM is desired to guarantee that correctly synchronized program would always run SC • All SC executions haven’t data races => the program haven’t non-SC executions
Happens-before consistency doesn’t guarantee SC for correctly synchronized programs • The program hasn’t synchronization actions!!! • But: some accesses to shared variables disappear in all SC executions (y = 1, x = 1), therefore in all SC executions the program has not data races – formally: correctly synchronized • HB consistency doesn’t work: may be non-SC executions
Safety guarantees needed for incorrectly synchronized programs • No out-of-thin-air reads • Type safety • Non-intrusive reads • When incorrectly synchronized reads are added to a correctly synchronized program (not altering semantics – debugging, etc.), the resulting program should still have sequentially consistent semantics, other than for the values seen by those reads • Causality • Prevents out-of-thin-air
No out-of-thin-air : intuition • Why causal loops may appear? • programs may speculatively perform a write, aborting and restarting things if, eventually, we don't commit/verify the write. • But in a multithreaded context, such speculation can wind up justifying itself : we may use static analysis and determine that there is some way to continue the execution such that the action could occur; then we can speculate that the event occurs in a way that allows the event to self-justify itself
Causality • Defines when reads can see future writes • JMM intuition: • If a write is absolutely certain to be performed in all executions, it may be seen early • If a write cannot occur unless it is seen by an earlier read, it cannot be seen by an earlier read
Causality – the essence of the formal definition • Each execution E must be derived by the series of well-formed (prefix) executions Ei.Ei may contain committed and not committed actions • An execution is well-formed if it is happens-before consistent, has total order on sync actions, obeys intra-thread semantics and some weak fairness of sync actions • Each next execution Ei+1 contains all actions of the previous execution Ei • Committed actions cannot un-commit in the next execution
Causality - continue • Reads that still haven’t committed must see only writes that happens-before • Committed reads must see writes just committed on previous steps • The value seen by not-committed read may change in the next (prefix) execution • Commitment “freezes” the value • Finally, in E all actions must be committed
Causality - strength • Enough weak: writes may commit presciently • Other words, writes may commit when happens-before reads wet aren’t “frozen” by commitment. • Enough strong • Correctly synchronized programs has SC behavior (commitment order must preserve intra-thread semantics, therefore in the above example of correctly synchronized program actions cannot be reordered) • The total commitment order prevents causal loops. On each step action a whether depends on previously committed one or a is the read depending on the write w that happens-before (and, therefore, w cannot depend on a).
An approximation of the JMM for the programmers : LRC • Monitors synchronization – LRC behavior • Volatile synchronization – was discussed, recently established LRC behavior, too • Total order over all synchronization actions Core JMM = + LRC Causality
Volatile synchronization • For each volatile, there is a total order over all accesses to that volatile.Question: how volatile accesses influence on the ordering of the ordinary data accesses? • Strong interpretation: There is a happens-before (or release/acquire) relationship from each write to each latter read of that volatile. • Weak interpretation: There is a happens-before (or release/acquire) relationship from each write to each latter read of that volatile that sees that write. • Some experts argued that weak interpretation is cheaper and it is sufficient for correctly synchronized code. However, expert group have agreed to adopt the strong semantics for volatiles.
Volatile synchronization: examples Initially, x = y = v = 0 v is a volatile variable Initially, x = y = v = 0 v is a volatile variable Behavior in question: r1 == r3 == 1 Decision: Allowed under the weak interpretation, forbidden under the strong interpretation Behavior in question: r1 = 1, r2 = 2, r3 = 2, r4 = 0Decision:Allowed under the weak interpretation, forbidden under the strong interpretation for volatile
Causality Test Cases (for JSR-133 draft):explanation on examples* Causality test case 1 Initially, x = y = 0 Behavior in question: r1 == r2 == 1 Decision: Allowed, since interthread compiler analysis could determine that x and y are always non-negative, allowing simplification of r1 >= 0 to true, and allowing write y = 1 to be moved early. Causality test case 2 Initially, x = y = 0 Behavior in question: r1 == r2 == r3 == 1 Decision: Allowed, since redundant read elimi-nation could result in simplification of r1 == r2 to true, allowing y = 1 to be moved early. Notes: In SC executions, both reads of x always return the same value * From the JMM mail list: http://www.cs.umd.edu/~pugh/java/memoryModel
Causality Test Cases (continue) Causality test case 3 Initially, x = y = 0 Behavior in question: r1 == r2 == r3 == 1 Decision: Allowed, since redundant read elimi-nation could result in simplification of r1 == r2 to true, allowing y = 1 to be moved early. Notes: Same as test case 2, except there are SC executions in which r1 != r2 Causality test case 4 Initially, x = y = 0 Behavior in question: r1 == r2 == 1 Decision: Forbidden: values are not allowed to come out of thin air
Causality Test Cases (continue) Causality test case 5 Initially, x = y = z = 0 Behavior in question: r1 == r2 == 1, r3 == 0 Decision: Forbidden: values are not allowed to come out of thin air, even if there are other executions in which the thin-air value would have been written to that variable by some not out-of-thin air means. Causality test case 6 Initially, A = B = 0 Behavior in question: r1 == r2 == 1 Decision: Allowed. Intrathread analysis could determine that thread 2 always writes 1 to A and hoist the write to the beginning of thread 2.
Causality Test Cases (continue) Causality test case 7 Initially, x = y = z = 0 Behavior in question: r1 = r2 = r3 = 1. Decision: Allowed. Intrathread transformations could move r1 = z to after the last statement in thread 1, and x = 1 to before the first statement in thread 2. Causality test case 8 Initially, x = y = 0 Behavior in question: r1 = r2 = 1 Decision: Allowed. Interthread analysis could determine that x and y are always either 0 or 1, and thus determine that r2 is always 1. Once this determination is made, the write of 1 to y could be moved early in thread 1.
Causality Test Cases (continue) Causality test case 9 Initially, x = y = 0 Behavior in question: r1 = r2 = 1 Decision: Allowed. Similar to test case 8, except that the x is not always 0 or 1. However, a compiler might determine that the read of x by thread 2 will never see the write by thread 3 (perhaps because thread 3 will be scheduled after thread 1). Thus, the compiler can determine that r1 will always be 0 or 1. Causality test case 9a Initially, x = 2, y = 0 Behavior in question: r1 = r2 = 1 Decision: Allowed. Similar to test case 8, except that the x is not always 0 or 1. However, a compiler might determine that thread 3 will always execute before thread 1, and that therefore the initial value of 2 will not be visible to the read of x in thread 1. Thus, the compiler can determine that r1 will always be 0 or 1.
Causality Test Cases (continue) Causality test case 10 Initially, x = y = z = 0 Behavior in question: r1 == r2 == 1, r3 == 0. Decision: Forbidden. This is the same as test case 5, except using control dependences rather than data dependences.
Causality Test Cases (continue) Causality test case 11 Initially, x = y = z = 0 Behavior in question: r1 = r2 = r3 = r4 = 1 Decision: Allowed. Reordering of independent statements can transform the code, after which the behavior in question is SC. Notes: This is similar to test case 7, but longer Causality test case 12 Initially, x = y = 0; a[0] = 1, a[1] = 2 Behavior in question: r1 = r2 = r3 = 1 Decision: Disallowed. Since no other thread accesses the array a, the code for thread 1 should be equivalent to: r1 = x; a[r1] = 0; if (r1 == 0) r2 = 0 else r2 = 1 y = r2 With this code, it is clear that this is the same situation as test 4.
Causality Test Cases (continue) Causality test case 13 Initially, x = y = 0 Behavior in question: r1 = r2 = 1 Decision: Disallowed. In all sequentially consistent executions, no writes to x or y occur and the program is correctly synchronized. The only SC behavior is r1 == r2 == 0. Causality test case 14 Initially, a = b = y = 0, y is volatile Behavior in question: r1 == r3 = 1; r2 = 0 Decision: Disallowed. In all sequentially consistent executions, r1 = 0 and the program is correctly synchronized. Since the program is correctly synchronized in all SC executions, no non-sc behaviors are allowed.
Causality Test Cases (continue) Causality test case 15 Initially, a = b = x = y = 0, x and y are volatile Behavior in question: r0 == r1 == r3 = 1; r2 == 0 Decision: Disallowed. In all sequentially consistent executions, r1 = 0 and the program is correctly synchronized. Since the program is correctly synchronized in all SC executions, no non-sc behaviors are allowed. Causality test case 16 Initially, x = y = 0 Behavior in question: r1 == 2; r2 == 1 Decision: Allowed Not coherent
Causality Test Cases (continue) Causality test case 17 Initially, x = y = 0 Behavior in question: r1 == r2 == r3 == 42 Decision: Allowed. A compiler could determine that at r1 = x in thread 1, it must be legal to read x and see the value 42. Changing r1 = x to r1 = 42 would allow y = r1 to be transformed to y = 42 and performed earlier, resulting in the behavior in question. Causality test case 18 Initially, x = y = 0 Behavior in question: r1 == r2 == r3 == 42 Decision: Allowed. A compiler could determine that the only legal values for x are 0 and 42. From that, the compiler could deduce that r3 != 0 implies r3 = 42. A compiler could then determine that at r1 = x in thread 1, it must be legal to read x and see the value 42. Changing r1 = x to r1 = 42 would allow y = r1 to be transformed to y = 42 and performed earlier, resulting in the behavior in question.
Causality Test Cases (continue) Causality test case 19 Initially, x = y = 0 Behavior in question: r1 == r2 == r3 == 42 Decision: Allowed. This is the same as test case 17, except that thread 1 has been split into two threads. Causality test case 20 Initially, x = y = 0 Behavior in question: r1 == r2 == r3 == 42 Decision: Allowed. This is the same as test case 18, except that thread 1 has been split into two threads.
Additional Test Cases for the Unified Model Causality test case U2 Initially, a = b = y = 0 Behavior in question: r1 == r3 == 1; r2 == 0 Decision: Prohibited. Test case U1 Initially, x = 1 and y = 0 Behavior in question: r1 == r2 == 2 Decision: Prohibited.
Additional Test Cases (continue) Causality test case U3 Initially, a = b = c = d = 0 Behavior in question: r1 == r3 == r4 ==1; r2 == 0 Decision: Allowed. Result of inlining of U4 (see below) Causality test case U3 Initially, a = b = y = 0 Behavior in question: r1 == r3 == 1; r2 == 0 Decision: Prohibited.
Additional Test Cases (continue) Causality test case U4 Initially, a = b = c = d = 0 Behavior in question: r1 == r3 == r4 == 1; r2 == 0 Decision: Prohibited.
Additional Test Cases (continue) Causality test case U6 Initially, x = y = z = 0, z is volatile Behavior in question: r1 == r2 == 1; r3 == 0 Decision: Prohibited.
Additional Test Cases (continue) Causality test case U7 Initially, x = y = z = 0 Behavior in question: r1 == 1; r2 == r3 == 2 Decision: Allowed.
Above the core JMM • Final field Semantic • Special cases • Write Protected Fields • Word Tearing • Double and long variables • Not guaranteed to be atomic • Fairness • Wait Sets and Notification • Sleep and yield • Haven’t any synchronization semantics • Finalization
Final Field Semantics • The value of a final field is not intended to change • Data race tolerate • Do not need synchronization if the references to the containing objects are not made visible to other threads during construction • Minimal cost when reading final field
Final fields: correctly published references f is a final field; its default value is 0 • If a reference to the object containing the final field is shared with other threads between the initial construction of the object and when deserialization changes the final fields of the object, guarantees for the final field don’t work Assume r2, r4 and r6 do not see value null. r3 and r5 can be 0 or 42, and r7 must be 42
Final fields: exceptional cases • It is planned to allow reflection to change final fields, assuming the appropriate security permissions; is needed for situations such as deserialization • Special rules would be applied while optimizing such code • Executing a block of code in a final field safe context (treated as a separate thread)
Special cases • Write Protected Fields • System.in, System.out, System.err: final static fields, but may be changed by special methods • Word Tearing • Not allowed: updates to one field or element do not interact with reads or updates of any other field or element