Transcript Bloch10
Effective Java:
Concurrency
Last Updated: Fall 2011
Agenda
Material From Joshua Bloch
Cover Items 66-73
Effective Java: Programming Language Guide
“Concurrency” Chapter
Bottom Line:
Primitive Java concurrency is complex
Concurrency in Java
Item 66: Synchronize Access
to Shared Mutable Data
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
Item 66: Unsafe Example
// 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++;}
Concurrency in Java
// Also note anonymous class
Item 66: Fixing the Example
// 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
Concurrency in Java
// Issue is communication, not mutual exclusion!
Item 66: A volatile Fix for
the Example
// 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
Item 66: volatile Does Not
Guarantee Mutual Exclusion
// 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
Item 66: Advice on Sharing
Data Between Threads
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
Item 67: Avoid Excessive
Synchronization
// 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
}}
More Item 67: What’s the
Problem?
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
More Item 67: Turning the
Alien Call into an Open Call
// 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
Concurrency in Java
Example: StringBuffer vs. StringBuilder
Item 67: Alternate Fix Using
CopyOnWriteArray
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
}}
Item 68: Prefer Executors and
Tasks to Threads
Old key abstraction: Thread
Unit of work and
Mechanism for execution
New key abstractions:
Task (Unit of work)
Mechanism for execution
Runnable and Callable
Executor Service
Start tasks, wait on particular tasks, etc.
See Bloch for references
Concurrency in Java
Item 69: Prefer Concurrency
Utilities to wait and notify
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
More Item 69: Timing Example
// 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
Item 70: Document Thread
Safety
Levels of Thread safety
Immutable:
Unconditionally thread-safe
Some methods require external synchronization
Example: Collections.synchronized wrappers
Not thread-safe
Instances of class are mutable, but is internally synchronized
Example: ConcurrentHashMap
Conditionally thread-safe
Instances of class appear constant
Example: String
Client responsible for synchronization
Examples: Collection classes
Thread hostile: Not to be emulated!
Concurrency in Java
More Item 70: Example
//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
Item 71: Use Lazy
Initialization Judiciously
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
More Item 71: Double Check
Lazy Initialization
// 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
Item 72: Don’t Depend on the
Thread Scheduler
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
Item 73: Avoid Thread Groups
Thread groups originally envisioned as a
mechanism for isolating Applets for
security purposes
Unfortunately, doesn’t really work
Concurrency in Java