Spring 2013
Material From Joshua Bloch
Effective Java: Programming Language Guide
Cover Items 66-73
“Concurrency” Chapter
Bottom Line:
Primitive Java concurrency is complex
Concurrency in Java
Method synchronization yields atomic transitions:
public synchronized boolean doStuff() {…}
Fairly well understood…
Method synchronization also ensures that other threads “see” earlier threads
Not synchronizing on shared “atomic” data produces wildly counterintuitive results
Not well understood
Concurrency in Java
// Broken! How long do you expect this program to run?
public class StopThread { private static boolean stopRequested ; public static void main (String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() { public void run() { // May run forever!
int i=o; while (! stopRequested ) i++; // See below
}}); backgroundThread.start();
TimeUnit.SECONDS.sleep(1); stopRequested = true;
} }
// Hoisting transform :
// while (!loopTest) {i++;} if (!loopTest) while(true) {i++;}
// Also note anonymous class Concurrency in Java
// As before, but with synchronized calls public class StopThread { private static boolean stopReq; public static synchronized void setStop() {stopReq = true;} public static synchronized void getStop() {return stopReq;} public static void main (String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() { public void run() { // Now “sees” main thread int i=o; while (! getStop() ) i++;
}}); backgroundThread.start();
TimeUnit.SECONDS.sleep(1); setStop();
} }
// Note that both setStop() and getStop() are synchronized
// Issue is communication, not mutual exclusion!
Concurrency in Java
// A fix with volatile public class StopThread {
// Pretty subtle stuff, using the volatile keyword private static volatile boolean stopRequested; public static void main (String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() { public void run() { int i=o; while (! stopRequested) i++;
}}); backgroundThread.start();
TimeUnit.SECONDS.sleep(1); stopRequested = true;
}
}
Concurrency in Java
// Broken! Requires Synchronization!
private static volatile int nextSerialNumber = 0; public static int generateSerialNumber() { return nextSerialNumber++;
}
Problem is that the “++” operator is not atomic
// Even better! (See Item 47) private static final AtomicLong nextSerial = new AtomicLong(); public static long generateSerialNumber() { return nextSerial.getAndIncrement();
}
Concurrency in Java
Share Immutable Data!
Confine mutable data to a single Thread
May modify, then share (no further changes)
Called “Effectively Immutable”
Allows for “Safe Publication”
Mechanisms for safe publication
In static field at class initialization volatile field final field field accessed with locking (ie synchronization)
Store in concurrent collection (Item 69)
Concurrency in Java
// Broken! Invokes alien method from sychronized block public interface SetOb<E> { void added(ObservableSet<E> set, E el);} public class ObservableSet<E> extends ForwardingSet<E> { // Bloch 16 public ObservableSet(Set<E> set) { super(set); } private final List<SetOb<E>> obs = new ArrayList<SetOb<E>>(); public void addObserver (SetObs<E> ob ) { synchronized (obs) { obs.add(ob); } } public boolean removeObserver (SetOb<E> ob ) { synchronized (obs) { return obs.remove(ob); } }
}} private void notifyElementAdded (E el) { synchronized(obs) { for (SetOb<E> ob:obs) // Exceptions?
ob.added(this, el); }
@Override public boolean add(E el) { // from Set interface boolean added = super.add(el); if (added) notifyElementAdded (el); return added;
Concurrency in Java
public static void main (String[] args) {
ObservableSet<Integer> set = new ObservableSet<Integer>
(new HashSet<Integer>); set.addObserver (new SetOb<Integer>() { public void added (ObservableSet<Integer> s, Integer e) {
System.out.println(e); if (e.equals(23)) s.removeObserver(this); // Oops! CME
// See Bloch for a variant that deadlocks instead of CME
});
}
} for (int i=0; i < 100; i++) set.add(i);
Concurrency in Java
// Alien method moved outside of synchronized block – open call private void notifyElementAdded(E el) {
List<SetOb<E>> snapshot = null; synchronized (observers) { snapshot = new ArrayList<SetOb<E>>(obs);
} for (SetObserver<E> observer : snapshot ) observer.added(this, el) // No more CME
}}
Open Calls increase concurrency and prevent failures
Rule: Do as little work inside synch block as possible
When designing a new class:
Do NOT internally synchronize absent strong motivation
Example: StringBuffer vs. StringBuilder
Concurrency in Java
public interface SetOb<E> { void added(ObservableSet<E> set, E el);} public class ObservableSet<E> extends ForwardingSet<E> { // Bloch 16 public ObservableSet(Set<E> set) { super(set); } private final List<SetOb<E>> obs = new
CopyOnWriteArrayList <SetOb<E>>(); public void addObserver (SetObs<E> ob ) { synchronized (obs) { obs.add(ob); } } public boolean removeObserver (SetOb<E> ob ) { synchronized (obs) { return obs.remove(ob); } }
}} private void notifyElementAdded (E el) {
{for (SetOb<E> ob:obs) // Iterate on copy – No Synch!
ob.added(this, el); }
@Override public boolean add(E el) { // from Set interface boolean added = super.add(el); if (added) notifyElementAdded (el); return added;
Concurrency in Java
Old key abstraction: Thread
Unit of work and
Mechanism for execution
New key abstractions:
Task (Unit of work)
Runnable and Callable
Mechanism for execution
Executor Service
Start tasks, wait on particular tasks, etc.
See Bloch for references
Concurrency in Java
wait() and notify() are complex
Java concurrency facilities much better
Legacy code still requires understanding low level primitives
Three mechanisms
Executor Framework (Item 68)
Concurrent collections
Internally synchronized versions of Collection classes
Extensions for blocking, Example: BlockingQueue
Synchronizers
Objects that allow Threads to wait for one another
Concurrency in Java
// Simple framework for timing concurrent execution public static long time (Executor executor, int concurrency, final Runnable action) throws InterrruptedExecution { final CountDownLatch ready = new CountDownLatch(concurrency); final CountDownLatch start = new CountDownLatch(1); final CountDownLatch done = new CountDownLatch(concurrency); for (int i=0; i< concurrency; i++) { executor.execute (new Runnable() { public void run() { ready.countDown(); // Tell Timer we’re ready try { start.await(); action.run(); // Wait till peers are ready
} catch (...){ ...}
} finally { done.countDown(); }} // Tell Timer we’re done
});} ready.await(); // Wait for all workers to be ready long startNanos = System.nanoTime(); start.countDown(); // And they’re off!
done.await() // Wait for all workers to finish return System.nanoTime() – startNanos;
}
Concurrency in Java
Levels of Thread safety
Immutable:
Instances of class appear constant
Example: String
Unconditionally thread-safe
Instances of class are mutable, but is internally synchronized
Example: ConcurrentHashMap
Conditionally thread-safe
Some methods require external synchronization
Example: Collections.synchronized wrappers
Not thread-safe
Client responsible for synchronization
Examples: Collection classes
Thread hostile: Not to be emulated!
Concurrency in Java
//Use Conditionally Thread-Safe Collections.synchronized wrapper
Map<K,V> m = Collections.synchronizedMap(new HashMap(K,V)());
...
Set<K> s = m.keySet(); // View needn’t be in synchronized block
... synchronized (m) { // Synchronizing on m, not s!
for (K key : s ) key.f(); // call f() on each key
// Documentation in Collections.synchronizedMap:
// “It is imperative that the user manually synchronize on the
// returned map when iterating over any of the collection views.”
Note that clients can (accidentally or intentionally) mount denial-of-service attacks on other users of m by synchronizing on m and then holding the lock. Private lock idiom thwarts this.
Concurrency in Java
Under most circumstances, normal initialization is preferred
// Normal initialization of an instance field private final FieldType field = computeFieldValue();
// Lazy initialization of instance field – synchronized accessor private FieldType field; synchronized FieldType getField() { if (field == null) field = computeFieldValue(); return field;
}
Concurrency in Java
// Double-check idiom for lazy initialization of instance fields private volatile FieldType field; // volatile key – see Item 66
FieldType getField() {
FieldType result = field; if (result == null) { // check with no locking synchronized (this) { result = field; if (result == null) // Second check with a lock field = result = computeFieldValue();
}
} return result;
}
Concurrency in Java
Any program that relies on the thread scheduler is likely to be unportable
Threads should not busy-wait
Use concurrency facilities instead (Item 69)
Don’t “Fix” slow code with
Thread.yield
calls
Restructure instead
Avoid Thread priorities
Concurrency in Java
Thread groups originally envisioned as a mechanism for isolating Applets for security purposes
Unfortunately, doesn’t really work
Concurrency in Java