2. About Me
Software Engineer at Barclays Capital
http://www.natureinspiredcode.com
http://github.com/natureinspired/8queen-ea
g-graphs
Twitter: @nature_inspired
3. JMM in a Nutshell
JMM is a universal contract for how threads
interact with memory in Java programs.
Happens-Before ordering: "official" reasoning
for thread interaction.
synchronized provides mutual exclusion and
memory visibility.
volatile provides atomic memory visibility.
4. public class SleepyThreadDemo {
private static boolean awake;
public static void main(String[] args) throws InterruptedException {
Thread sleepyThread = new Thread(new Runnable() {
public void run() {
int zCount = 0;
while(!awake) {
zCount++;
}
}
});
sleepyThread.start();
TimeUnit.SECONDS.sleep(1);
awake = true;
}
}
5. public class SleepyThreadDemo {
private static boolean awake;
public static void main(String[] args) throws InterruptedException {
Thread sleepyThread = new Thread(new Runnable() {
public void run() {
int zCount = 0;
if (!awake) {
while(true) {
zCount++;
}
}
}
});
sleepyThread.start();
TimeUnit.SECONDS.sleep(1);
awake = true;
}
6. Causality
Cause
Symmetric Multiprocessing (SMP)
Memory Ordering
Memory Barriers
Compiler Optimisation
Effect
History
Objectives
Happens-Before Ordering
Concurrency Constructs
12. Memory Ordering
1. CPU 0 executes a=1.
int a = 1; 2. CPU 0 looks “a” up in the cache, and finds
int b = a + 1; that it is missing.
assert(b == 2) 3. CPU 0 therefore sends a “read invalidate” mes-
sage in order to get exclusive ownership of the
cache line containing “a”.
4. CPU 0 records the store to “a” in its store
buffer.
5. CPU 1 receives the “read invalidate” message,
and responds by transmitting the cache line
and removing that cacheline from its cache.
13. Memory Ordering
6. CPU 0 starts executing the b=a+1.
int a = 1;
7. CPU 0 receives the cache line from CPU 1,
int b = a + 1; which still has a value of zero for “a”.
assert(b == 2) 8. CPU 0 loads “a” from its cache, finding the value
zero.
9. CPU 0 applies the entry from its store queue to
the newly arrived cache line, setting the value of “a”
in its cache to one.
10. CPU 0 adds one to the value zero loaded for
“a” above, and stores it into the cache line
containing “b” (which we will assume is already
owned by CPU 0).
11. CPU 0 executes assert(b==2), which fails.
14. Memory Ordering 1. CPU 0 executes a=1. The cache line
is not in CPU 0’s cache, so CPU 0
places the new value of “a” in its store
@Singleton buffer and transmits a “read invalidate”
class FooBar { message.
2. CPU 1 executes while(b==0)continue,
private int a, b; but the cache line containing “b” is not
in its cache. It therefore transmits a
“read” message.
public void foo() { 3. CPU 0 executes b=1. It already owns
a = 1; this cache line, so it stores the new
b = 1; value of “b” in its cache line.
} 4. CPU 0 receives the “read” message,
and transmits the cache line
public void bar() { containing the now-updated value of
while(b==0) continue; “b” to CPU 1, also marking the line as
assert(a==1); “shared” in its own cache.
}
}
15. Memory Ordering 5. CPU 1 receives the cache line
containing “b” and installs it in its cache.
6. CPU 1 can now finish executing while
(b==0) continue, and since it finds that
@Singleton the value of “b” is 1, it proceeds to the
class FooBar { next statement.
7. CPU 1 executes the assert(a==1),
private int a, b; and, since CPU 1 is working with the old
value of “a”, this assertion fails.
public void foo() { 8. CPU 1 receives the “read invalidate”
a = 1; message, and transmits the cache line
containing “a” to CPU 0 and invalidates
b = 1;
this cache line from its own cache. But it
} is too late.
9. CPU 0 receives the cache line
public void bar() { containing “a” and applies the buffered
while(b==0) continue; store just in time to fall victim to CPU 1’s
assert(a==1); failed assertion.
}
}
16. 1. CPU 0 executes a=1. The cache line is not in
Memory Ordering CPU 0’s cache, so CPU 0 places the new
value of “a” in its store buffer and transmits a
“read invalidate” message.
@Singleton 2. CPU 1 executes while(b==0)continue, but
class FooBar { the cache line containing “b” is not in its
cache. It therefore transmits a “read”
message.
private int a, b; 3. CPU 0 executes fence(), and marks all
current store-buffer entries (namely, the
a=1).
public void foo() { 4. CPU 0 executes b=1. It already owns this
a = 1; cache line, but there is a marked entry in the
fence(); store buffer. Therefore, rather than store the
new value of “b” in the cache line, it instead
b = 1; places it in the store buffer (but in an
} unmarked entry).
5. CPU 0 receives the “read” message, and
trans- mits the cache line containing the
public void bar() { original value of “b” to CPU 1. It also marks
while(b==0) continue; its own copy of this cache line as “shared”.
assert(a==1); 6. CPU 1 receives the cache line containing “b”
and installs it in its cache.
} 7. CPU 1 can now finish executing while(b==0)
continue, but since it finds that the value of
} 'b' i still 0, it repeats the while statement. The
new value of 'b' is safely hidden in CPU 0's
store buffer.
17. Memory Fencing 8. CPU 1 receives the “read invalidate”
message, and transmits the cache line
containing 'a' to CPU 0 and invalidates this
@Singleton cache line from its own cache.
class FooBar { 9. CPU 0 receives the cache line containing
'a' and applies the buffered store.
10. Since the store to 'a' was the only entry in
private int a, b; the store buffer that was marked by fence(),
CPU 0 can also store the new value 'b' -
expect for the fact that the cache line
public void foo() { containing 'b' is now in 'shared' state.
a = 1; 11. CPU 0 therefore sends an 'invalidate
fence(); message to CPU 1'.
12. CPU 1 receives the 'invalidate' message,
b = 1; invalidates the cache line containing 'b' from
} its cache, and sends an 'acknowledgement'
message to CPU 0.
13. CPU 1 executes while(b==0)continue,
public void bar() { but the cache line containing 'b' is not in
while(b==0) continue; cache. It therefore transmits a 'read' message
assert(a==1); to CPU 0.
}
}
18. Memory Fencing 14. CPU 0 receives the 'acknowledgement'
message, and puts the cache line containing 'b'
@Singleton into the 'exclusive' state. CPU 0 now stores the
new value of 'b' into the cache line.
class FooBar { 15. CPU 0 receives the 'read' message, and
transmits the cache line containing the original
private int a, b; value of 'b' to CPU 1. It also marks its own copy
of this cache as 'shared'.
16. CPU 1 receives the cache line containing 'b'
public void foo() { and installs it in its cache.
a = 1; 17. CPU 1 can now finish executing while
(b==0)continue and since it finds that the value
fence(); of 'b' is 1, it proceeds to the next statement.
b = 1; 18. CPU 1 executes the assert(a==1) but the
} cache line containing 'a' is no longer in its
cache. Once it gets this cache from CPU 0, it
will be working with the up-to-date value of 'a',
public void bar() { thus assertion passes.
while(b==0) continue;
assert(a==1);
}
}
19. Compiler Optimisations
Loop Fusion
int i = 0, size = 100;
int[] a = new int[size], b = new int[size];
for (i = 0; i < size; i++) {
a[i] = 1;
}
for (i = 0; i < size; i++) {
b[i] = 2;
}
20. Compiler Optimisations
Loop Fusion
int i = 0, size = 100;
int[] a = new int[size], b = new int[size];
for (i = 0; i < size; i++) {
a[i] = 1;
b[i] = 2;
}
22. Compiler Optimisations
Loop Fission
int i = 0, size = 100;
int[] a = new int[size], b = new int[size];
for (i = 0; i < size; i++) {
a[i] = 1;
}
for (i = 0; i < size; i++) {
b[i] = 2;
}
27. What is a Memory Model
A model to describe thread interaction with memory.
Multiprocessors have different memory models.
Inconsistently weak
Enables to instruct memory fencing.
Helps to write date race free
Preserving program order.
28. Java Memory Model
A Memory Model for consistent thread behaviour with
memory for all CPU architectures
Alpha
RISC
PowerPC
x86
AMD64
Pioneering
Influential
29. Java Memory Model
History
Initially broken
volatile was not very volatile
final was not very final
Developers originally worked around locks.
Huge open research area
Constantly finding new ways to improve the JMM.
People looking to go towards different ways of reasoning.
30. Java Memory Model
Goals
1. Allow as many compiler and hardware optimisations as
possible.
2. Provide balance between optimisations and correctly
synchronised code.
3. Provide developers to write and reason about multithreaded
code.
32. Java's synchronized keyword
public class BankAccount {
private BigDecimal total = new BigDecimal(0);
public BigDecimal withdraw(BigDecimal amount) {
total = total.minus(amount);
return total;
}
public BigDecimal deposit(BigDecimal amount) {
total = total.add(amount);
return total;
}
public BigDecimal total() { return total; }
}
33. Symmetrical Multiprocessing
deposit(200) withdraw(100) total() == 0
T1 T2 T3
BankAccount account = new BankAccount();
34. Java's synchronized keyword
public class BankAccount {
private BigDecimal total = new BigDecimal(0);
public synchronized BigDecimal withdraw(BigDecimal amount) {
total = total.minus(amount);
return total;
}
public synchronized BigDecimal deposit(BigDecimal amount) {
total = total.add(amount);
return total;
}
public synchronized BigDecimal total() {return total;}
}
35. Symmetrical Multiprocessing
deposit(200) withdraw(100) total() == 100
T1 T2 T3
BankAccount account = new BankAccount();
36. Java's volatile keyword
@Singleton
class FooFactory {
private Foo foo;
public Foo getFoo() {
if (foo == null) {
synchronized(this) {
if (foo == null) {
foo = new Foo("bar");
}
}
}
return foo;
37. Java's volatile keyword
class Foo {
private String name;
public Foo(String name) {
setName(name);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
41. Java's volatile keyword
@ThreadSafe
class FooFactory {
private static class FooHelper {
public static Foo foo = new Foo();
}
public static Foo getFoo() {
return FooHelper.foo;
}
}
42. Java's final keyword
class MeaningOfLife {
private final int answer;
public MeaningOfLife(int answer){
this.answer = answer;
}
public int answer() {
return this.answer;
}
}
43. Final
int answer = 42 int answer = 42 int answer = 42
MeaningOfLife life = new MeaningOfLife(42);
44. Summary
Memory ordering can change the program
order of multithreaded code.
Memory barriers inhibits optimisations for
correct synchronisation.
Java Concurrency Constructs: ensures mutual
exclusion and memory visibility
Happens-Before ordering: any subsequent
lock acquisitions will see changes made by
previous lock releases.