Post on 10-May-2015
transcript
Concurrency Antipatternsin IDEA
Почему все криво,
ничего не работает и падает
Plan
• Theory– Concurrency primitives
• Antipatterns found in IDEA– Bad code– How to fix– What happened
• Bits of advice
Concurrency primitives
Mutual exclusion/general:• synchronized blocks – use always• j.u.c.locks.Lock – only for guruIn addition, for data protection:• j.u.c.* collections - use always• Atomic* classes – use rarely • volatile fields – never use
Antipattern
volatile T myCached;
public T getData() {
if (myCached == null) {
myCached = compute();
}
return myCached;
}
void clearCaches() {
myCached = null;
}
How to fix
/*not volatile*/ T myCached;
public synchronized T getData() {
if (myCached == null) {
myCached = compute();
}
return myCached;
}
void synchronized clearCaches() {
myCached = null;
}
Moral
• volatile properties– Visibility– Disallowed reordering– But: more expensive access
• Use volatile only when value do not depend on previous state or other variables, e.g.– Boolean flag myInitializedThe only state change is false->true
– Object publicationState change: null->initialized object
– Couple of other use cases$5 off salary per every usage
Antipattern
private T myInstance;
public T getData() {
if (myInstance == null) {
synchronized (this) {
if (myInstance == null) {
myInstance = init();
}}
}
return myInstance;
}
How to fix
private volatile T myInstance;
public T getData() {
if (myInstance == null) {
synchronized (this) {
if (myInstance == null) {
myInstance = init();
}}
}
return myInstance;
}
Moral
Why DCL is broken:
myInstance = new T();
(compiles to)
m = alloc(T.class);
m.x = 2; //inline constructor
myInstance = m;
(if myInstance not volatile, can reorder)
m = alloc(T.class);
myInstance = m; //not initialized obj escapes
m.x = 2;
5 min whipping for using DCL
Antipattern
private T myData;
public void populateCache() {
synchronized (lock) {
myData = compute(); }
}
public T getData() {
return myData;
}
How to fix
private T myData;
public void populateCache() {
synchronized (lock) {
myData = compute(); }
}
public T getData() {
synchronized (lock) {
return myData; }
}
Moral
Half-baked synchronization
Both threads must synchronize on same lock for changes to be visible
$10 off salary per every usage
Antipattern
private T myData;
public MyClass() {
myData = init(); }
}
public T getData() {
return myData;
}
How to fix
private final T myData;
public MyClass() {
myData = init(); }
}
public T getData() {
return myData;
}
Moral
Safe object publication
Java undertakes special measures to make final fields safely initialized and visible after constructor execution.
knocking head against table 5 times for every non final field
Antipatternprivate T myCollection;
public void addItem() { //once in a blue moon
try { lock.lock();
myCollection.addSomething();
} finally { lock.unlock(); }}
public T findSomething() { //constantly
try { lock.lock();
return myCollection.findSomething();
} finally { lock.unlock(); }
}
How to fixprivate T myCollection;
public void addItem() { //once in a blue moon
try { lock.getWriteLock().lock();
myCollection.addSomething();
} finally { lock.getWriteLock().unlock(); }}
public T findSomething() { //constantly
try { lock.getReadLock().lock();
return myCollection.findSomething();
} finally { lock.getReadLock().unlock(); }
}
Moral
Read/Write locking
Use when write access is infrequent, whereas read access is overwhelmingly typical.
E.g. listener lists, some caches.
5 minutes of contempt for each overlooked usage
Antipattern (not really)
private int myCounter;
public synchronized void increment() {
myCounter++;
}
public synchronized int getValue() {
return myCounter;
}
How to /*fix*/ simplify
private final AtomicInteger myCounter;
public void increment() {
myCounter.incrementAndGet();
}
public int getValue() {
return myCounter.get();
}
Moral
Atomic* classes are handy• Implemented via hardware-supported CAS (compare and swap) instruction
• Cheap under low contention• Can saturate processor bus in multicore environment and high contention
Bits of advice
• Use CopyOnWriteArrayList instead of synchronized accessors for rarely modified lists, e.g. listeners
• Use ConcurrentHashMap instead of Collections.synchronizedMap()
Bits of advice
Is ReentrantLock better than synchronized?+1 More scalable under high contention
+1 More features: interruptible, timed wait, polling multiple, not structured
-1 Some clever optimizations are not yet available for Locks: lock coarsening, lock elision, adaptive spinning
-1 Ugly error-prone syntax
Resolution: use *Locks only if need advanced features, otherwise stick with synchronized. Profile!
Bugs are everywhere
For more concurrency antipatterns, see IDEA source base.
Read Brian Goetz – he is clever.• More flexible, scalable locking in JDK5:
– www.ibm.com/developerworks/java/library/j-jtp10264
• Managing volatility:– www.ibm.com/developerworks/java/library/j-jtp06197.html