210 likes | 223 Views
Effective Java: Concurrency. Spring 2013. Agenda. Material From Joshua Bloch Effective Java: Programming Language Guide Cover Items 66-73 “Concurrency” Chapter Bottom Line: Primitive Java concurrency is complex. Item 66: Synchronize Access to Shared Mutable Data.
E N D
Effective Java:Concurrency Spring 2013
Agenda • Material From Joshua Bloch • Effective Java: Programming Language Guide • Cover Items 66-73 • “Concurrency” Chapter • Bottom Line: • Primitive Java concurrency is complex
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
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++;} // 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 // 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; } }
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(); }
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)
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; }}
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); }
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 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; }}
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) • Runnable and Callable • Mechanism for execution • Executor Service • Start tasks, wait on particular tasks, etc. • See Bloch for references
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
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; }
Item 70: Document Thread Safety • 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!
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.
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; }
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; }
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
Item 73: Avoid Thread Groups • Thread groups originally envisioned as a mechanism for isolating Applets for security purposes • Unfortunately, doesn’t really work