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

Droidcon SF Advanced Techniques for concurrency and memory management

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)

8ec22ebc6cddbfbe097a580b8d685f75?s=128

Nabil Hachicha

March 18, 2016
Tweet

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. VISIBILITY & ATOMICITY

  5. VOLATILE ▸ Reads/Writes go directly to main memory ▸ No

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

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

    are read from memory Thread B: int counter = sharedObject.counter; int nonVolatile = sharedObject.nonVolatile;
  8. PREFER VOLATILE OVER LOCKS IF POSSIBLE

  9. ▸ Less runtime overhead ▸ volatile operations will never block

    (scalability over lock)
  10. EX: SWITCHING A FLAG class Flag { private boolean flag

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

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

    public synchronized void toggle() { flag = !flag; } public synchronized boolean getFlag() { return flag; } }
  13. 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; } }
  14. 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(); } } }
  15. 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; } }
  16. GC & REFERENCE API

  17. WEAKHASHMAP AKA LEAKMAP FOR SOME Map<Object, String> map = new

    WeakHashMap<Object, String>(); Object key = new Object(); map.put(key, "xyz"); key = null;
  18. BE CAREFUL WITH THE CHOICE OF KEYS

  19. 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());
  20. LITERAL STRING WeakHashMap<String, Object> map = new WeakHashMap<String, Object>(); String

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

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

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

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

    0 ; i < 100 ; i++) { WeakReference<Object> ref = new WeakReference<Object>(new Object(), queue); }
  28. 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 } }
  29. 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); } }
  30. FINALIZER ▸ Can resurrect the original object ▸ Require 2

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

    Signal that the Referent has been finalized ▸ Referent is always null (prevent resurrection)
  32. PHANTOM REFERENCE Scenario: detect a memory leak

  33. 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; } }
  34. 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; } }
  35. class Service { Activity.Listener listener; void registerListener (Activity.Listener listener) {

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

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

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

    // removed the strong reference Runtime.getRuntime().gc();
  39. ASSERT NO MEMORY LEAK Reference<?> ref = referenceQueue .remove(TimeUnit.SECONDS.toMillis(10)); assertNotNull(ref);

    // Activity is GC'ed ref.clear();
  40. 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();
  41. 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
  42. IdentityHashMap ▸ Regular Map

  43. IdentityHashMap ▸ Regular Map ▸ Reference-equality in place of object-equality

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

    for key & value ▸ Faster if you have a complex hashCode it uses System.identityHashCode()
  45. 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
  46. ASYNCHRONOUS MODELS

  47. Activity - Service COMMUNICATION ▸ Activity start service for background

    work ▸ Service need to communicate results ▸ Service may be in another process
  48. Messenger 1. Activity creates a Handler

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

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

    from a Handler 3. new Messenger(handler)
  51. 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
  52. 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
  53. 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
  54. ResultReceiver 1. Activity creates a Handler

  55. ResultReceiver 1. Activity creates a Handler 2. create a ResultReceiver

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

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

    using the Handler 3. new ResultReceiver(handler) 4. override onReceiveResult to receive msg
  58. 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
  59. 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
  60. SYNCHRONIZERS

  61. CountDownLatch 1. Initialize counter with # participants

  62. CountDownLatch 1. Initialize counter with # participants 2. await() sleeps

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

    until counter == 0 3. every thread call countDown()
  64. CyclicBarrier 1. Similar to CountDownLatch 2. await() sleep until all

    participants reaches the barrier
  65. EXAMPLE CALCULATE THE AVERAGE OF 3 RANDOM INT

  66. CyclicBarrier controller = new CyclicBarrier(NB_THREADS + 1); // account for

    main thread
  67. for (int i = 0; i < NB_THREADS; i++) {

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

    queue.add(random.nextInt()); controller.await(); } }
  69. REUSING THE BARRIER

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

  71. class Aggregate implements Runnable { @Override public void run() {

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

    new Thread(new Task()).start(); }
  73. 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(); } }
  74. 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)
  75. Phaser phaser = new Phaser(1);

  76. for (int i = 0; i < n; i++) {

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

    queue.add(random.nextInt()); phaser.arrive(); } }
  78. CONCLUSION

  79. UNDERSTAND YOUR PROBLEM your architecture is not the list of

    tools/lib — Uncle Bob
  80. UNDERSTANDING IMPLEMENTATION & TIME/ SPACE COMPLEXITY

  81. BE OPINIONATED DON'T FOLLOW TRENDS

  82. 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
  83. Q/A REALM.IO/JOBS @NABIL_HACHICHA +NABILHACHICHA