Java Concurrency Gotchas

11,170 views

Published on

An overview of Java Concurrency bug patterns. JavaOne 2009 presentation.

Published in: Technology, Business

Java Concurrency Gotchas

  1. 1. Java™ Platform Concurrency Gotchas Alex Miller Terracotta
  2. 2. Questions to answer > What are common concurrency problems? > Why are they problems? > How do I detect these problems? > How do I correct these problems? 2
  3. 3. Taxonomy of Concurrency Gotchas > Shared Data > Coordination > Performance 3
  4. 4. Shared Data > Locking > Visibility > Atomicity > Safe Publication 4
  5. 5. What happens if we modify data without locking? 5
  6. 6. What happens if we modify data without locking? Hint: itʼs not good. 5
  7. 7. 6
  8. 8. Mutable Statics public class MutableStatics { FORMAT is mutable private static final DateFormat FORMAT = DateFormat.getDateInstance(DateFormat.MEDIUM); public static Date parse(String str) throws ParseException { ...and this mutates it return FORMAT.parse(str); outside synchronization } public static void main(String arg[]) { MutableStatics.parse(“Jan 1, 2000”); } } 7
  9. 9. Mutable Statics - instance per call public class MutableStatics { public static Date parse(String str) throws ParseException { DateFormat format = DateFormat.getDateInstance(DateFormat.MEDIUM); return format.parse(str); } public static void main(String arg[]) { MutableStatics.parse(“Jan 1, 2000”); } } 8
  10. 10. Synchronization private int myField; synchronized( What goes here? ) { myField = 0; } 9
  11. 11. DO NOT synchronize on null MyObject obj = null; synchronized( obj ) { NullPointerException! // stuff } 10
  12. 12. DO NOT change instance MyObject obj = new MyObject(); synchronized( obj ) { No longer synchronizing obj = new MyObject(); on the same object! } 11
  13. 13. 12
  14. 14. DO NOT synchronize on string literals private static final String LOCK = “LOCK”; synchronized( LOCK ) { What is the scope // work of LOCK? } 13
  15. 15. DO NOT synchronize on autoboxed instances private static final Integer LOCK = 0; synchronized( LOCK ) { What is the scope // work of LOCK? } 14
  16. 16. DO NOT synchronize on ReentrantLock final Lock lock = new ReentrantLock(); synchronized( lock ) { Probably not what // work you meant here } final Lock lock = new ReentrantLock(); lock.lock(); Probably should try { be this instead // ... } finally { lock.unlock(); } 15
  17. 17. What should I lock on? // The field you are protecting private final Map map = ... synchronized(map) { // ...access map } // Or an explicit lock object private final Object lock = new Object(); synchronized(lock) { // ... modify state } 16
  18. 18. Visibility 17
  19. 19. Inconsistent Synchronization public class SomeData { private final Map data = new HashMap(); public void put(String key, String value) { synchronized(data) { data.put(key, value); Modified under synchronization } } public String get(String key) { return data.get(key); Read without synchronization } } 18
  20. 20. Double-checked locking public final class Singleton { private static Singleton instance; public static Singleton getInstance() { if(instance == null) { Attempt to avoid synchronization synchronized(Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; } } 19
  21. 21. Double-checked locking public final class Singleton { private static Singleton instance; public static Singleton getInstance() { if(instance == null) { READ synchronized(Singleton.class) { if(instance == null) { READ instance = new Singleton(); WRITE } } } return instance; } } 19
  22. 22. Double-checked locking - volatile public class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { if(instance == null) { synchronized(Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return INSTANCE; } 20
  23. 23. Double-checked locking - initialize on demand public class Singleton { private static class SingletonHolder { private static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } } 21
  24. 24. Racy single-check public final class String { private int hash; // default to 0 private final char[] value; // immutable public int hashCode() { int h = hash; if(h == 0) { // ... compute value for h from data hash = h; } return h; } } 22
  25. 25. volatile arrays public final class VolatileArray { private volatile boolean[] vals; public void flip(int i) { vals[i] = true; Is the value of vals[i] } visible to other threads? public boolean flipped(int i) { return vals[i]; } } 23
  26. 26. Atomicity 24
  27. 27. Volatile counter public class Counter { private volatile int count; public int next() { return count++; Looks atomic to me! } } 25
  28. 28. AtomicInteger counter public class Counter { private final AtomicInteger count = new AtomicInteger(); public int next() { Really atomic by return count.getAndIncrement(); encapsulating } multiple actions } 26
  29. 29. Composing atomic actions public Object putIfAbsent( Hashtable table, Object key, Object value) { Hashtable is thread-safe if(table.containsKey(key)) { READ // already present, return existing value return table.get(key); READ } else { // doesn’t exist, create and return new value table.put(key, value); WRITE return value; } } 27
  30. 30. Composing atomic actions public Object putIfAbsent( Hashtable table, Object key, Object value) { Hashtable is thread-safe if(table.containsKey(key)) { READ // already present, return existing value return table.get(key); READ } else { // doesn’t exist, create and return new value table.put(key, value); WRITE return value; } } 27
  31. 31. Participate in lock public Object putIfAbsent( Hashtable table, Object key, Object value) { synchronized(table) { Protect with synchronization if(table.containsKey(key)) { return table.get(key); } else { table.put(key, value); return value; } } } 28
  32. 32. Encapsulated compound actions public Object putIfAbsent( ConcurrentHashMap table, Object key, Object value) { table.putIfAbsent(key, value); } 29
  33. 33. Assignment of 64 bit values public class LongAssignment { private long x; public void setLong(long val) { x = val; Looks atomic to me - } but is it? } 30
  34. 34. Assignment of 64 bit values - volatile public class LongAssignment { private volatile long x; public void setLong(long val) { x = val; } } 31
  35. 35. Safe publication Intentionally left blank. 32
  36. 36. Listener in constructor public interface DispatchListener { void newFare(Customer customer); } public class Taxi implements DispatchListener { public Taxi(Dispatcher dispatcher) { dispatcher.registerListener(this); We just published a // other initialization reference to this - oops! } public void newFare(Customer customer) { // go to new customer's location } } 33
  37. 37. Starting thread in constructor public class Cache { private final Thread cleanerThread; public Cache() { this escapes again! cleanerThread = new Thread(new Cleaner(this)); cleanerThread.start(); } // Cleaner calls back to this method public void cleanup() { // clean up Cache } } 34
  38. 38. Static factory method public class Cache { // ... public static Cache newCache() { Cache cache = new Cache(); cache.startCleanerThread(); return cache; } } 35
  39. 39. Coordination > Threads > wait/notify 36
  40. 40. Threads > DO NOT: • Call Thread.stop() • Call Thread.suspend() or Thread.resume() • Call Thread.destroy() • Call Thread.run() • Use ThreadGroups 37
  41. 41. wait/notify // Thread 1 synchronized(lock) { You must synchronize. while(! someCondition()) { Always wait in a loop. lock.wait(); } } // Thread 2 synchronized(lock) { Synchronize here too. satisfyCondition(); Update condition! lock.notifyAll(); } 38
  42. 42. Performance > Deadlock > Spin wait > Lock contention 39
  43. 43. Deadlock // Thread 1 synchronized(lock1) { synchronized(lock2) { Classic deadlock. // stuff } } // Thread 2 synchronized(lock2) { synchronized(lock1) { // stuff } } 40
  44. 44. Deadlock avoidance > Lock splitting > Lock ordering > Lock timeout > tryLock 41
  45. 45. Spin wait // Not efficient private volatile boolean flag = false; public void waitTillChange() { while(! flag) { Spin on flag, Thread.sleep(100); waiting for change } } public void change() { flag = true; } 42
  46. 46. Replace with wait/notify private final Object lock = new Object(); private boolean flag = false; public void waitTillChange() { synchronized(lock) { while(! flag) Wait/notify is far more lock.wait(); efficient than spin wait. } } public void change() { synchronized(lock) { flag = true; lock.notifyAll(); } } 43
  47. 47. Lock contention x Hash f(x) Bucket 0 Bucket 1 Bucket 2 Bucket 3 44
  48. 48. Lock striping x Hash g(x) Hash f(x) Hash f(x) Bucket 0 Bucket 1 Bucket 0 Bucket 1 45
  49. 49. Final Exam public class StatisticsImpl implements Statistics, StatisticsImplementor { private long queryExecutionCount; public synchronized void queryExecuted(String hql, int rows, long time) { queryExecutionCount++; // ... other stat collection } public long getQueryExecutionCount() { return queryExecutionCount; } public synchronized void clear() { queryExecutionCount = 0; // ... clear all other stats } } 46
  50. 50. Final Exam public class StatisticsImpl implements Statistics, StatisticsImplementor { private long queryExecutionCount; public synchronized void queryExecuted(String hql, int rows, long time) { queryExecutionCount++; // ... other stat collection } public long getQueryExecutionCount() { return queryExecutionCount; Read of shared value } without synchronization public synchronized void clear() { queryExecutionCount = 0; // ... clear all other stats } } 46
  51. 51. Final Exam public class StatisticsImpl implements Statistics, StatisticsImplementor { private long queryExecutionCount; public synchronized void queryExecuted(String hql, int rows, long time) { queryExecutionCount++; // ... other stat collection } public long getQueryExecutionCount() { return queryExecutionCount; Read of shared value Non-atomic read } without synchronization of long value public synchronized void clear() { queryExecutionCount = 0; // ... clear all other stats } } 46
  52. 52. Final Exam public class StatisticsImpl implements Statistics, StatisticsImplementor { private long queryExecutionCount; public synchronized void queryExecuted(String hql, int rows, long time) { queryExecutionCount++; // ... other stat collection } public long getQueryExecutionCount() { return queryExecutionCount; Read of shared value Non-atomic read } without synchronization of long value public synchronized void clear() { queryExecutionCount = 0; Race condition if reading stat and // ... clear all other stats clearing - could be compound action } } 46
  53. 53. Final Exam public class StatisticsImpl implements Statistics, StatisticsImplementor { private long queryExecutionCount; public synchronized void queryExecuted(String hql, int rows, long time) { Single shared lock for ALL stat values queryExecutionCount++; // ... other stat collection } public long getQueryExecutionCount() { return queryExecutionCount; Read of shared value Non-atomic read } without synchronization of long value public synchronized void clear() { queryExecutionCount = 0; Race condition if reading stat and // ... clear all other stats clearing - could be compound action } } 46
  54. 54. Alex Miller Terracotta Blog: http://tech.puredanger.com Twitter: @puredanger

×