Slide 1

Slide 1 text

ADVANCED TECHNIQUES FOR CONCURRENCY & MEMORY MANAGEMENT Nabil Hachicha - Realm @nabil_hachicha March-2016 - DroidconSF

Slide 2

Slide 2 text

REALM HTTPS://REALM.IO/ ▸ Designed for mobile ▸ ZERO-COPY Object store ▸ NoSQLite, C++ column based core ▸ MVCC Multiple concurrency Read

Slide 3

Slide 3 text

AGENDA ▸ Visibility & Atomicity ▸ Reference API & GC ▸ Asynchronous communication ▸ Synchronizers ▸ Q/A

Slide 4

Slide 4 text

VISIBILITY & ATOMICITY

Slide 5

Slide 5 text

VOLATILE ▸ Reads/Writes go directly to main memory ▸ No CPU Cache ▸ Guarantees visibility of changes across threads

Slide 6

Slide 6 text

HAPPENS-BEFORE ▸ volatile write = previous (even non volatile) vars, are flushed into memory Thread A: sharedObject.nonVolatile = 123; sharedObject.counter = sharedObject.counter + 1;

Slide 7

Slide 7 text

HAPPENS-BEFORE ▸ volatile read = subsequent (even non volatile) vars, are read from memory Thread B: int counter = sharedObject.counter; int nonVolatile = sharedObject.nonVolatile;

Slide 8

Slide 8 text

PREFER VOLATILE OVER LOCKS IF POSSIBLE

Slide 9

Slide 9 text

▸ Less runtime overhead ▸ volatile operations will never block (scalability over lock)

Slide 10

Slide 10 text

EX: SWITCHING A FLAG class Flag { private boolean flag = true; public void toggle() { flag = !flag; } public boolean getFlag() { return flag; } }

Slide 11

Slide 11 text

NON THREAD SAFE class Flag { private boolean flag = true; public void toggle() { // Unsafe flag = !flag; } public boolean getFlag() { // Unsafe return flag; } }

Slide 12

Slide 12 text

PESSIMISTIC LOCKING class Flag { private boolean flag = true; public synchronized void toggle() { flag = !flag; } public synchronized boolean getFlag() { return flag; } }

Slide 13

Slide 13 text

IF READS >> WRITE, COMBINE VOLATILE AND INTRINSIC LOCK class Flag { private volatile boolean flag = true; public synchronized void toggle() { flag = !flag; } // cheap read-write lock trick public boolean getFlag() { return flag; } }

Slide 14

Slide 14 text

IF READS >> WRITE, USE ReadWriteLock class Flag { private boolean flag = true; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); public void toggle() { writeLock.lock(); try { flag = !flag; } finally { writeLock.unlock(); } } public boolean getFlag() { readLock.lock(); try { return flag; } finally { readLock.unlock(); } } }

Slide 15

Slide 15 text

USING ATOMIC* API class Flag { private AtomicBoolean flag = new AtomicBoolean(true); public void toggle() { // Optimistic lock (CompareAndSwap) boolean old; do { old = flag.get(); } while (!flag.compareAndSet(old, !old)); } public AtomicBoolean getFlag() { return flag; } }

Slide 16

Slide 16 text

GC & REFERENCE API

Slide 17

Slide 17 text

WEAKHASHMAP AKA LEAKMAP FOR SOME Map map = new WeakHashMap(); Object key = new Object(); map.put(key, "xyz"); key = null;

Slide 18

Slide 18 text

BE CAREFUL WITH THE CHOICE OF KEYS

Slide 19

Slide 19 text

DEFENSIVE COPY com.google.android.gms.maps.model.Polygon polygon = ...; Map, Object> map = new WeakHashMap, Object>(); map.put(polygon.getPoints(), new Object());

Slide 20

Slide 20 text

LITERAL STRING WeakHashMap map = new WeakHashMap(); String key = "I'm pooled!"; map.put(key, new Object()); key = null;

Slide 21

Slide 21 text

BOXED TYPE Map map = new WeakHashMap(); for (int i=0; i < 128; i++) { map.put(i, new Object()); }

Slide 22

Slide 22 text

BOXED TYPE private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; }

Slide 23

Slide 23 text

java.lang.ref.Reference ▸ Object pointing to another Object (referent) ▸ Can be GC'ed anytime ▸ Used to observe the lifecycle of an Object ▸ Soft, Weak & Phantom for different Use Cases

Slide 24

Slide 24 text

java.lang.ref.Reference Object referent = new Object (); Reference reference = new WeakReference(referent);

Slide 25

Slide 25 text

java.lang.ref.Reference Object referent = new Object (); Reference reference = new WeakReference(referent); referent = null; Runtime.getRuntime().gc(); // Hint to run the GC

Slide 26

Slide 26 text

java.lang.ref.Reference Object referent = new Object (); Reference reference = new WeakReference(referent); referent = null; Runtime.getRuntime().gc(); // Hint to run the GC if (reference.get() == null) { //The garbage collector deleted my referent } else { // referent object is still here }

Slide 27

Slide 27 text

ReferenceQueue ReferenceQueue queue = new ReferenceQueue(); for (int i = 0 ; i < 100 ; i++) { WeakReference ref = new WeakReference(new Object(), queue); }

Slide 28

Slide 28 text

ReferenceQueue ReferenceQueue queue = new ReferenceQueue(); for (int i = 0 ; i < 100 ; i++) { WeakReference ref = new WeakReference(new Object(), queue); for (int i = 0 ; i < 100 ; i++) { WeakReference ref = new WeakReference(new Object(), queue); Reference> r; while ((r = queue.poll()) != null) { // polling to discover GC'ed referent // reference 'r' cleared } }

Slide 29

Slide 29 text

ReferenceQueue Set> refs = new HashSet>(); ReferenceQueue queue = new ReferenceQueue(); for (int i = 0 ; i < 100 ; i++) { WeakReference ref = new WeakReference(new Object(), queue); refs.add(ref); for (int i = 0 ; i < 100 ; i++) { WeakReference ref = new WeakReference(new Object(), queue); Reference> r; while ((r = queue.poll()) != null) { // polling to discover GC'ed referent // reference 'r' cleared refs.remove(r); } }

Slide 30

Slide 30 text

FINALIZER ▸ Can resurrect the original object ▸ Require 2 GC cycles ▸ Slow, one thread performing finalization

Slide 31

Slide 31 text

PHANTOM REFERENCE ! ▸ Always used with a ReferenceQueue ▸ Signal that the Referent has been finalized ▸ Referent is always null (prevent resurrection)

Slide 32

Slide 32 text

PHANTOM REFERENCE Scenario: detect a memory leak

Slide 33

Slide 33 text

class Activity { interface Listener {} Service service; Activity(Service service) { this.service = service; } void onStart() { service.registerListener(new Listener() {}); //Listener hold a reference to Activity } void onStop () { service.unregisterListener(); service = null; } }

Slide 34

Slide 34 text

class Activity { interface Listener {} Service service; Activity(Service service) { this.service = service; } void onStart() { service.registerListener(new Listener() {}); //Listener hold a reference to Activity } void onStop () { service.unregisterListener(); service = null; } }

Slide 35

Slide 35 text

class Service { Activity.Listener listener; void registerListener (Activity.Listener listener) { this.listener = listener; } void unregisterListener () { this.listener = null; } }

Slide 36

Slide 36 text

ASSERT NO MEMORY LEAK Service service = new Service(); Activity activity = new Activity(service); activity.onStart();

Slide 37

Slide 37 text

ASSERT NO MEMORY LEAK // ... ReferenceQueue referenceQueue = new ReferenceQueue(); PhantomReference reference = new PhantomReference (service.listener, referenceQueue);

Slide 38

Slide 38 text

ASSERT NO MEMORY LEAK // ... activity.onStop(); activity = null; // removed the strong reference Runtime.getRuntime().gc();

Slide 39

Slide 39 text

ASSERT NO MEMORY LEAK Reference> ref = referenceQueue .remove(TimeUnit.SECONDS.toMillis(10)); assertNotNull(ref); // Activity is GC'ed ref.clear();

Slide 40

Slide 40 text

Service service = new Service(); Activity activity = new Activity(service); activity.onStart(); ReferenceQueue referenceQueue = new ReferenceQueue(); PhantomReference reference = new PhantomReference(service.listener, referenceQueue); activity.onStop(); activity = null; // removed the strong reference Runtime.getRuntime().gc(); Reference> ref = referenceQueue.remove(TimeUnit.SECONDS.toMillis(10)); assertNotNull(ref); ref.clear();

Slide 41

Slide 41 text

WRITE ~ DETERMINISTIC TESTS INVOLVING GC Pattern used by AOSP (FinalizationTester.java) and 1 LeakCanary2 lib3 3 https://github.com/square/leakcanary/blob/master/leakcanary-watcher/src/main/java/com/squareup/leakcanary/RefWatcher.java 2 https://github.com/square/leakcanary/blob/master/leakcanary-watcher/src/main/java/com/squareup/leakcanary/GcTrigger.java 1 https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/java/lang/ref/FinalizationTester.java

Slide 42

Slide 42 text

IdentityHashMap ▸ Regular Map

Slide 43

Slide 43 text

IdentityHashMap ▸ Regular Map ▸ Reference-equality in place of object-equality for key & value

Slide 44

Slide 44 text

IdentityHashMap ▸ Regular Map ▸ Reference-equality in place of object-equality for key & value ▸ Faster if you have a complex hashCode it uses System.identityHashCode()

Slide 45

Slide 45 text

IdentityHashMap ▸ Regular Map ▸ Reference-equality in place of object-equality for key & value ▸ Faster if you have a complex hashCode it uses System.identityHashCode() ▸ Support mutable key

Slide 46

Slide 46 text

ASYNCHRONOUS MODELS

Slide 47

Slide 47 text

Activity - Service COMMUNICATION ▸ Activity start service for background work ▸ Service need to communicate results ▸ Service may be in another process

Slide 48

Slide 48 text

Messenger 1. Activity creates a Handler

Slide 49

Slide 49 text

Messenger 1. Activity creates a Handler 2. create a messenger from a Handler

Slide 50

Slide 50 text

Messenger 1. Activity creates a Handler 2. create a messenger from a Handler 3. new Messenger(handler)

Slide 51

Slide 51 text

Messenger 1. Activity creates a Handler 2. create a messenger from a Handler 3. new Messenger(handler) 4. pass the Messenger as a Bundle argument

Slide 52

Slide 52 text

Messenger 1. Activity creates a Handler 2. create a messenger from a Handler 3. new Messenger(handler) 4. pass the Messenger as a Bundle argument 5. Service invoke Messenger.send

Slide 53

Slide 53 text

Messenger 1. Activity creates a Handler 2. create a messenger from a Handler 3. new Messenger(handler) 4. pass the Messenger as a Bundle argument 5. Service invoke Messenger.send 6. Handler#handleMessage get Message

Slide 54

Slide 54 text

ResultReceiver 1. Activity creates a Handler

Slide 55

Slide 55 text

ResultReceiver 1. Activity creates a Handler 2. create a ResultReceiver using the Handler

Slide 56

Slide 56 text

ResultReceiver 1. Activity creates a Handler 2. create a ResultReceiver using the Handler 3. new ResultReceiver(handler)

Slide 57

Slide 57 text

ResultReceiver 1. Activity creates a Handler 2. create a ResultReceiver using the Handler 3. new ResultReceiver(handler) 4. override onReceiveResult to receive msg

Slide 58

Slide 58 text

ResultReceiver 1. Activity creates a Handler 2. create a ResultReceiver using the Handler 3. new ResultReceiver(handler) 4. override onReceiveResult to receive msg 5. pass the ResultReceiver as a Bundle args

Slide 59

Slide 59 text

ResultReceiver 1. Activity creates a Handler 2. create a ResultReceiver using the Handler 3. new ResultReceiver(handler) 4. override onReceiveResult to receive msg 5. pass the ResultReceiver as a Bundle args 6. Service invoke ResultReceiver.send

Slide 60

Slide 60 text

SYNCHRONIZERS

Slide 61

Slide 61 text

CountDownLatch 1. Initialize counter with # participants

Slide 62

Slide 62 text

CountDownLatch 1. Initialize counter with # participants 2. await() sleeps until counter == 0

Slide 63

Slide 63 text

CountDownLatch 1. Initialize counter with # participants 2. await() sleep until counter == 0 3. every thread call countDown()

Slide 64

Slide 64 text

CyclicBarrier 1. Similar to CountDownLatch 2. await() sleep until all participants reaches the barrier

Slide 65

Slide 65 text

EXAMPLE CALCULATE THE AVERAGE OF 3 RANDOM INT

Slide 66

Slide 66 text

CyclicBarrier controller = new CyclicBarrier(NB_THREADS + 1); // account for main thread

Slide 67

Slide 67 text

for (int i = 0; i < NB_THREADS; i++) { new Thread(new Task()).start(); } // wait at the barrier until all threads finishes controller.await(); computeAverage();

Slide 68

Slide 68 text

class Task implements Runnable { @Override public void run() { queue.add(random.nextInt()); controller.await(); } }

Slide 69

Slide 69 text

REUSING THE BARRIER

Slide 70

Slide 70 text

CyclicBarrier cyclicBarrier = new CyclicBarrier(NB_THREADS, new Aggregate());

Slide 71

Slide 71 text

class Aggregate implements Runnable { @Override public void run() { // All threads arrived at barrier computeAverage(); // clear the queue for reuse queue.clear(); } }

Slide 72

Slide 72 text

for (int i = 0; i < NB_THREADS; i++) { new Thread(new Task()).start(); }

Slide 73

Slide 73 text

class Task implements Runnable { public void run() { queue.add(random.nextInt()); cyclicBarrier.await(); // reusing the barrier assert queue.size() == 0; queue.add(random.nextInt()); cyclicBarrier.await(); } }

Slide 74

Slide 74 text

Phaser 1. Thread register() to increment # of participants 2. Thread arrive() similar to countDown() 3. Barrier/Phase is crossed when NUMBER(REGISTERED THREAD) = NUMBER(ARRIVED THREAD)

Slide 75

Slide 75 text

Phaser phaser = new Phaser(1);

Slide 76

Slide 76 text

for (int i = 0; i < n; i++) { phaser.register(); new Thread(new Task()).start(); } // wait until all registered threads arrives phaser.arriveAndAwaitAdvance(); computeAverage();

Slide 77

Slide 77 text

class Task implements Runnable { @Override public void run() { queue.add(random.nextInt()); phaser.arrive(); } }

Slide 78

Slide 78 text

CONCLUSION

Slide 79

Slide 79 text

UNDERSTAND YOUR PROBLEM your architecture is not the list of tools/lib — Uncle Bob

Slide 80

Slide 80 text

UNDERSTANDING IMPLEMENTATION & TIME/ SPACE COMPLEXITY

Slide 81

Slide 81 text

BE OPINIONATED DON'T FOLLOW TRENDS

Slide 82

Slide 82 text

Thank You! Reference & Credits: - The CERT® Oracle® Secure Coding Standard for Java™ - Java Concurrency in Practice - Jakob Jenkov - Java Volatile Keywordblog blog http://tutorials.jenkov.com/java-concurrency/volatile.html

Slide 83

Slide 83 text

Q/A REALM.IO/JOBS @NABIL_HACHICHA +NABILHACHICHA