the java memory model. jmm: sc intuition may fail application programmer supposes sequential...
Post on 22-Dec-2015
223 views
TRANSCRIPT
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
Writingthread
Readingthread
… …
WRITE X, V1 READ Y, U2
WRITE Y, U1 READ X, V1
WRITE X, V2
WRITE Y, U2
… …
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
thread
local copies
master copies
use assign
read
load storewrite
lockunlock
Execution engine
Working memory for each thread
Main memory
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
The double check idiom*// 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
* 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
• 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
Thread 1Thread 2
r1 = new Object();
synchronized (r1) {
A = 1;
}
Synchronized (r1) {
B = 1;
}
r2 = new Object();
synchronized (r2) {
r3 = B;
}
Synchronized (r2) {
r4 = A;
}
Initially, A == B ==0
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’ → RProblem: happens-before consistency doesn’t provide SC for correctly synchronized programsProblem: 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
Thread 1Thread 2
r1 = xr2 = y
if (r1 != 0)if (r2 != 0)
y = 1 x = 1
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
The requirement of the total (virtual) commit order prevents the action from causing 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
Thread 1:Thread 2:
r1 = xr3 = y
v = 0v = 0
r2 = vr4 = v
y = 1x = 1
Initially, x = y = v = 0v is a volatile variable
Behavior in question: r1 == r3 == 1
Decision: Allowed under the weak interpretation, forbidden under the strong interpretation
Thread 1:
Thread 2:
Thread 3:
Thread 4:
x = 1y = 1r1 = vr3 = vv = 1v = 2r2 = vr4 = x
Behavior in question: r1 = 1, r2 = 2, r3 = 2, r4 = 0
Decision: Allowed under the weak interpretation, forbidden under the strong interpretation for volatile
Initially, x = y = v = 0v is a volatile variable
Causality Test Cases (for JSR-133 draft):explanation on examples*
Thread 1Thread 2
r1 = x r2 = y
if r1 >= 0x = r2
y = 1
* From the JMM mail list: http://www.cs.umd.edu/~pugh/java/memoryModel
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
Thread 1Thread 2
r1 = xr3 = y
r2 = x x = r3
if r1 == r2
y = 1
Causality Test Cases (continue)
Thread 1Thread 2Thread 3
r1 = xr3 = yx = 2
r2 = x x = r3
if r1 == r2
y = 1
Thread 1Thread 2
r1 = xr2 = y
y = r1 x = r2
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)
Thread 1Thread 2Thread 3Thread 4
r1 =x r2 =x z = 1r3 = z
y = r1y = r2x = r3
Thread 1Thread 2
r1 = Ar2 = B
if (r1 == 1)if (r2 == 1)
B = 1 A = 1
if (r2 == 0)
A = 1
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)
Thread 1Thread 2
r1 = zr3 = y
r2 = xz = r3
y = r2x = 1
Thread 1Thread 2
r1 = xr3 = y
r2 = 1 + r1 * r1 – r1x = r3
y = r2
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)
Thread 1Thread 2Thread 3
r1 = xr3 = yx = 2
r2 = 1 + r1 * r1 – r1x = r3
y = r2
Causality test case 9a Initially, x = 2, y = 0
Behavior in question: r1 = r2 = 1Decision: 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.
Thread 1Thread 2Thread 3
r1 = xr3 = yx = 0
r2 = 1 + r1 * r1 – r1x = r3
y = r2
Causality test case 9 Initially, x = y = 0
Behavior in question: r1 = r2 = 1Decision: 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 Cases (continue)
Thread 1Thread 2Thread 3Thread 4
r1 = xr2 = yz = 1r3 = z
if (r ==1)if(r2==1)if(r3==1)
y = 1x = 1x = 1
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)
Thread 1Thread 2
r1 = zr4 = w
w = r1r3 = y
r2 = xz = r3
y = r2x = 1
Thread 1Thread 2
r1 = xr3 = y
a[r1] = 0x = r3
r2 = a[0]
y = r2
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)
Thread 1Thread 2
r1 = xr2 = y
if (r1 == 1)if (r2 == 1)
y = 1 x = 1
Thread 1Thread 2
r1 = ado {
if (r1 == 0) r2 = y
y = 1 r3 = b
else } while (r2 + r3 == 0)
b = 1a = 1
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)
Thread 1Thread 2Thread 3
r0 = xdo { x = 1
if (r0 == 1) r2 = y
r1 = a r3 = b
else} while (r2 + r3 == 0)
r1 = 0a = 1
if (r1 == 0)
y = 1
else
b = 1
Thread 1Thread 2
r1 = xr2 = x
x = 1x = 2
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)
Thread 1Thread 2
r3 = xr2 = y
if (r3 != 42)x = r2
x = 42
r1 = x
y = r1
Thread 1Thread 2
r3 = xr2 = y
if (r3 == 0)x = r2
x = 42
r1 = x
y = r1
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)
Thread 1Thread 2Thread 3
join thread 3r2 = yr3 = x
r1 = xx = r2if (r3 != 42)
y = r1 x = 42
Thread 1Thread 2Thread 3
join thread 3r2 = yr3 = x
r1 = xx = r2if (r3 == 0)
y = r1 x = 42
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
Thread 1Thread 2
r1 = xr2 = y
y = r1if (r2 > 0)
x = 2
Thread 1Thread 2
r1 = ar2 = y
if (r1 == 0)r3 = b
y = 1if (r2 + r3 != 0)
else a = 1
b = 1
Test case U1 Initially, x = 1 and y = 0
Behavior in question: r1 == r2 == 2 Decision: Prohibited.
Causality test case U2 Initially, a = b = y = 0
Behavior in question: r1 == r3 == 1; r2 == 0Decision: Prohibited.
Additional Test Cases (continue)
Thread 1Thread 2
r1 = ado {
if (r1 == 0) r2 = y
y = 1 r3 = b
else } while (r2 + r3 == 0)
b = 1a = 1
Causality test case U3 Initially, a = b = y = 0
Behavior in question: r1 == r3 == 1; r2 == 0Decision: Prohibited.
Causality test case U3 Initially, a = b = c = d = 0
Behavior in question: r1 == r3 == r4 ==1; r2 == 0Decision: Allowed. Result of inlining of U4 (see below)
Thread 1/2/3Thread 4
r1 = a
if (r1 == 0)
b = 1r4 = d
r2 = bif (r4 == 1) {
if (r2 == 1) c = 1
c = 1 a = 1
r3 = c}
if (r3 == 1)
d = 1
Additional Test Cases (continue)
Thread 1Thread 2Thread 3Thread 4
r1 = ar2 = br3 = cr4 = d
if (r1 == 0)if (r2 == 1)if (r3 == 1)if (r4 == 1) {
b = 1 c = 1 d = 1 c = 1
a = 1
}
Causality test case U4 Initially, a = b = c = d = 0
Behavior in question: r1 == r3 == r4 == 1; r2 == 0Decision: Prohibited.
Additional Test Cases (continue)
Thread 1Thread 2Thread 3Thread 4
join Thread 4join Thread 4r3 = z
r1 = xr2 = yz = 1if (r3 == 1) {
y = r1x = r2 x = 1
y = 1
}
Causality test case U6 Initially, x = y = z = 0, z is volatile
Behavior in question: r1 == r2 == 1; r3 == 0Decision: Prohibited.
Additional Test Cases (continue)
Thread 1Thread 2Thread 3Thread 4
join Thread 1
join Thread 2
x = 1x = 2r1 = xr3 = z
y = r1y = r3
r2 = y
z = r2
Causality test case U7 Initially, x = y = z = 0
Behavior in question: r1 == 1; r2 == r3 == 2Decision: 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
• 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
Thread 1Thread 2Thread 3
r1.f = 42r2 = pr6 = q
p = r1r3 = r2.fr7 = r6.f
freeze r1.fr4 = q
q = r1if (r2 == r4)
r5 = r4.f
Assume r2, r4 and r6 do not see value null. r3 and r5 can be 0 or 42, and r7 must be 42
f is a final field; its default value is 0
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
Fairness
• JMM has weak fairness guarantees for synchronization actions. Only a finite number of synchronization actions may occur before a given synchronization action occurs in the synchronization order
Thread 1Thread 2
while (!v);v = true;
print "done";
Initially, v is volatile and v is false
If we observe the print messagefrom Thread 2, and no otherthreads are running, Thread 1 must see the write to v and terminate
Fairness: implementation issues
• If there exists some thread that is able to make progress, some thread will make progress
• Compilers cannot, in general, hoist volatile reads out of potentially infinite loops
• Compilers cannot, in general, reorder synchronization actions and externally visible actions
Wait Sets and Notification
• Every object has an associated wait set. A wait set is a set of threads
• Elementary actions that add threads to and remove threads from wait sets are atomic
• Wait sets are manipulated through Object.wait, Object.notify, Object.notifyAll
• Wait sets are affected by interruption
– Notifications cannot be lost due to interrupts
• Sleeping and joining have properties derived from those of wait and notification actions
Interaction of waits, notification and interruption
• If a thread is both notified and interrupted while waiting, it may either:– Return normally from wait, while still having a pending interrupt
– Return from wait by throwing an InterruptedException
• Assume that a set S of threads is in the wait set of a monitor M, and another thread performs a notify on M. Then either– At least one thread in S returns normally from wait (without
throwing InterruptedException), or
– All of the threads in S must exit wait by throwing InterruptedException
Further reading
• JMM web page http://www.cs.umd.edu/~pugh/java/memoryModel/
• java.util.concurrent package http://g.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html