This document discusses concurrency and concurrent programming in Java. It introduces the built-in concurrency primitives like wait(), notify(), synchronized, and volatile. It then discusses higher-level concurrency utilities and data structures introduced in JDK 5.0 like Executors, ExecutorService, ThreadPools, Future, Callable, ConcurrentHashMap, CopyOnWriteArrayList that provide safer and more usable concurrency constructs. It also briefly covers topics like Java Memory Model, memory barriers, and happens-before ordering.
14. Future and Callable -- Representing asynchronous tasks interface ArchiveSearcher { String search(String target); } class App { ExecutorService executor = ... ArchiveSearcher searcher = ... void showSearch(final String target) throws InterruptedException { Future<String> future = executor.submit( new Callable<String>() { public String call() { return searcher.search(target); }}); displayOtherThings(); // do other things while searching try { displayText( future.get() ); // use future } catch (ExecutionException ex) { cleanup(); return; } } } Callable is functional analog of Runnable interface Callable<V> { V call() throws Exception; } Sometimes, you want to start your threads asynchronously and get the result of one of the threads when you need it. Future holds result of asynchronous call, normally a Callable The result can only be retrieved using method get() when the computation has completed, blocking if necessary until it is ready. cancel(boolean) isDone() isCancelled()
23. interface ConcurrentMap<K, V> extends Map<K, V> boolean replace(K key, V oldValue, V newValue); Replaces the entry for a key only if currently mapped to a given value. This is equivalent to if (map.containsKey(key) && map.get(key).equals(oldValue)) { map.put(key, newValue); return true; } else return false; except that the action is performed atomically. V replace(K key, V value); Replaces the entry for a key only if currently mapped to some value. This is equivalent to if (map.containsKey(key)) { return map.put(key, value); } else return null; except that the action is performed atomically. V putIfAbsent (K key, V value); If the specified key is not already associated with a value, associate it with the given value. This is equivalent to if (!map.containsKey(key)) return map.put(key, value); else return map.get(key); except that the action is performed atomically. boolean remove(Object key, Object value); Removes the entry for a key only if currently mapped to a given value. This is equivalent to if (map.containsKey(key) && map.get(key).equals(value)) { map.remove(key); return true; } else return false; except that the action is performed atomically.
24.
25. Figure 1: Throughput of ConcurrentHashMap vs a synchronized HashMap under moderate contention as might be experienced by a frequently referenced cache on a busy server.
26.
27.
28. CopyOnWriteArrayList List<String> listA = new CopyOnWriteArrayList<String>(); List<String> listB = new ArrayList<String>(); listA.add("a"); listA.add("b"); listB.addAll(listA); Iterator itrA = listA.iterator(); Iterator itrB = listB.iterator(); listA.add("c"); //listB.add("c");//ConcurrentModificationException for (String s:listA){ System. out .println("listA " + s);//a b c } for (String s:listB){ System. out .println("listB " + s);//a b } while (itrA.hasNext()){ System. out .println("listA iterator " + itrA.next());//a b //itrA.remove();//java.lang.UnsupportedOperationException } while (itrB.hasNext()){ System. out .println("listB iterator " + itrB.next());//a b } The returned iterator provides a snapshot of the state of the list when the iterator was constructed. No synchronization is needed while traversing the iterator. The iterator does NOT support the remove, set or add methods. public Iterator<E> iterator() { return new COWIterator <E>(getArray(), 0); } public ListIterator<E> listIterator() { return new COWIterator<E>(getArray(), 0); }
36. 图 1. synchronized 和 Lock 的吞吐率,单 CPU 图 2. synchronized 和 Lock 的吞吐率(标准化之后), 4 个 CPU
37.
38.
39.
40.
41.
42.
43. CyclicBarrier thread1 thread2 thread3 CyclicBarrier barrier = new CyclicBarrier(3); await() await() await() class Solver { final int N; final float[][] data; final CyclicBarrier barrier ; class Worker implements Runnable { int myRow; Worker(int row) { myRow = row; } public void run() { while (!done()) { processRow(myRow); try { barrier.await (); } catch (InterruptedException ex) { return; } catch (BrokenBarrierException ex) { return; } } } } public Solver(float[][] matrix) { data = matrix; N = matrix.length; barrier = new CyclicBarrier(N, new Runnable() { public void run() { mergeRows(...); } }); for (int i = 0; i < N; ++i) new Thread(new Worker(i)).start(); waitUntilDone (); } } A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released. int await() Waits until all {@linkplain #getParties parties} have invoked
44.
45. CountDownLatch class Driver2 { // ... void main() throws InterruptedException { CountDownLatch doneSignal = new CountDownLatch(N); Executor e = ... for (int i = 0; i < N; ++i) // create and start threads e.execute(new WorkerRunnable(doneSignal, i)); doneSignal.await(); // wait for all to finish } } class WorkerRunnable implements Runnable { private final CountDownLatch doneSignal; private final int i; WorkerRunnable(CountDownLatch doneSignal, int i) { this.doneSignal = doneSignal; this.i = i; } public void run() { try { doWork(i); doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } void doWork() { ... } } Another typical usage would be to divide a problem into N parts, describe each part with a Runnable that executes that portion and counts down on the latch, and queue all the Runnables to an Executor. When all sub-parts are complete, the coordinating thread will be able to pass through await.
HashMaps can be very important on server applications for caching purposes. And as such, they can receive a good deal of concurrent access. Before Java 5, the standard HashMap implementation had the weakness that accessing the map concurrently meant synchronizing on the entire map on each access. Synchronizing on the whole map fails to take advantage of a possible optimisation: because hash maps store their data in a series of separate 'buckets', it is in principle possible to lock only the portion of the map that is being accessed. This optimisation is generally called lock striping . Java 5 brings a hash map optimised in this way in the form of ConcurrentHashMap. A combination of lock striping plus judicious use of volatile variables gives the class two highly concurrent properties: Writing to a ConcurrentHashMap locks only a portion of the map; Reads can generally occur without locking .