Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Droidcon SF Advanced Techniques for concurrency...

Droidcon SF Advanced Techniques for concurrency and memory management

Code samples:
PhantomReference: https://gist.github.com/nhachicha/4ba780712a7ae179cc67
Synchronizers: https://gist.github.com/nhachicha/7596e912c81aae38d721

Managing memory & concurrency are one of the biggest challenges we face when it comes to writing testable & efficient apps.
based on the experience of building Realm, We'll dig into some patterns & tricks used to control memory footprint & multithreading.

Some of the topics this talks will cover are:
Volatile, Atomic*, Reference API (WeakReference & ReferenceQueue) to work with the GC
Alternative asynchronous models (Messenger/ResultReceiver)

Nabil Hachicha

March 18, 2016
Tweet

More Decks by Nabil Hachicha

Other Decks in Programming

Transcript

  1. ADVANCED TECHNIQUES FOR CONCURRENCY & MEMORY MANAGEMENT Nabil Hachicha -

    Realm @nabil_hachicha March-2016 - DroidconSF
  2. REALM HTTPS://REALM.IO/ ▸ Designed for mobile ▸ ZERO-COPY Object store

    ▸ NoSQLite, C++ column based core ▸ MVCC Multiple concurrency Read
  3. AGENDA ▸ Visibility & Atomicity ▸ Reference API & GC

    ▸ Asynchronous communication ▸ Synchronizers ▸ Q/A
  4. VOLATILE ▸ Reads/Writes go directly to main memory ▸ No

    CPU Cache ▸ Guarantees visibility of changes across threads
  5. HAPPENS-BEFORE ▸ volatile write = previous (even non volatile) vars,

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

    are read from memory Thread B: int counter = sharedObject.counter; int nonVolatile = sharedObject.nonVolatile;
  7. EX: SWITCHING A FLAG class Flag { private boolean flag

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

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

    public synchronized void toggle() { flag = !flag; } public synchronized boolean getFlag() { return flag; } }
  10. 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; } }
  11. 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(); } } }
  12. 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; } }
  13. WEAKHASHMAP AKA LEAKMAP FOR SOME Map<Object, String> map = new

    WeakHashMap<Object, String>(); Object key = new Object(); map.put(key, "xyz"); key = null;
  14. DEFENSIVE COPY com.google.android.gms.maps.model.Polygon polygon = ...; Map<List<LatLng>, Object> map =

    new WeakHashMap<List<LatLng>, Object>(); map.put(polygon.getPoints(), new Object());
  15. LITERAL STRING WeakHashMap<String, Object> map = new WeakHashMap<String, Object>(); String

    key = "I'm pooled!"; map.put(key, new Object()); key = null;
  16. BOXED TYPE Map<Integer, Object> map = new WeakHashMap<Integer, Object>(); for

    (int i=0; i < 128; i++) { map.put(i, new Object()); }
  17. BOXED TYPE private static class IntegerCache { static final int

    low = -128; static final int high; static final Integer cache[]; }
  18. 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
  19. java.lang.ref.Reference Object referent = new Object (); Reference<Object> reference =

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

    new WeakReference<Object>(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 }
  21. ReferenceQueue ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); for (int i =

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

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

    ReferenceQueue<Object>(); for (int i = 0 ; i < 100 ; i++) { WeakReference<Object> ref = new WeakReference<Object>(new Object(), queue); refs.add(ref); for (int i = 0 ; i < 100 ; i++) { WeakReference<Object> ref = new WeakReference<Object>(new Object(), queue); Reference<?> r; while ((r = queue.poll()) != null) { // polling to discover GC'ed referent // reference 'r' cleared refs.remove(r); } }
  24. FINALIZER ▸ Can resurrect the original object ▸ Require 2

    GC cycles ▸ Slow, one thread performing finalization
  25. PHANTOM REFERENCE ! ▸ Always used with a ReferenceQueue ▸

    Signal that the Referent has been finalized ▸ Referent is always null (prevent resurrection)
  26. 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; } }
  27. 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; } }
  28. class Service { Activity.Listener listener; void registerListener (Activity.Listener listener) {

    this.listener = listener; } void unregisterListener () { this.listener = null; } }
  29. ASSERT NO MEMORY LEAK Service service = new Service(); Activity

    activity = new Activity(service); activity.onStart();
  30. ASSERT NO MEMORY LEAK // ... ReferenceQueue<Activity.Listener> referenceQueue = new

    ReferenceQueue<Activity.Listener>(); PhantomReference<Activity.Listener> reference = new PhantomReference<Activity.Listener> (service.listener, referenceQueue);
  31. ASSERT NO MEMORY LEAK // ... activity.onStop(); activity = null;

    // removed the strong reference Runtime.getRuntime().gc();
  32. Service service = new Service(); Activity activity = new Activity(service);

    activity.onStart(); ReferenceQueue<Activity.Listener> referenceQueue = new ReferenceQueue<Activity.Listener>(); PhantomReference<Activity.Listener> reference = new PhantomReference<Activity.Listener>(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();
  33. 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
  34. IdentityHashMap ▸ Regular Map ▸ Reference-equality in place of object-equality

    for key & value ▸ Faster if you have a complex hashCode it uses System.identityHashCode()
  35. 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
  36. Activity - Service COMMUNICATION ▸ Activity start service for background

    work ▸ Service need to communicate results ▸ Service may be in another process
  37. Messenger 1. Activity creates a Handler 2. create a messenger

    from a Handler 3. new Messenger(handler)
  38. 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
  39. 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
  40. 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
  41. ResultReceiver 1. Activity creates a Handler 2. create a ResultReceiver

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

    using the Handler 3. new ResultReceiver(handler) 4. override onReceiveResult to receive msg
  43. 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
  44. 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
  45. CountDownLatch 1. Initialize counter with # participants 2. await() sleep

    until counter == 0 3. every thread call countDown()
  46. for (int i = 0; i < NB_THREADS; i++) {

    new Thread(new Task()).start(); } // wait at the barrier until all threads finishes controller.await(); computeAverage();
  47. class Task implements Runnable { @Override public void run() {

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

    // All threads arrived at barrier computeAverage(); // clear the queue for reuse queue.clear(); } }
  49. for (int i = 0; i < NB_THREADS; i++) {

    new Thread(new Task()).start(); }
  50. 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(); } }
  51. 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)
  52. for (int i = 0; i < n; i++) {

    phaser.register(); new Thread(new Task()).start(); } // wait until all registered threads arrives phaser.arriveAndAwaitAdvance(); computeAverage();
  53. class Task implements Runnable { @Override public void run() {

    queue.add(random.nextInt()); phaser.arrive(); } }
  54. 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