680 likes | 780 Views
Concurrency Utilities in Practice. Using java.util.concurrent. Tim Peierls Prior Artisans, LLC. Goals. Review concurrency utilities Present several applications Measure and evaluate performance. Speakers. Tim Peierls is a member of the JSR 166 Expert Group. Examples. PseudoRandom
E N D
Concurrency Utilities in Practice Using java.util.concurrent Tim Peierls Prior Artisans, LLC
Goals • Review concurrency utilities • Present several applications • Measure and evaluate performance
Speakers Tim Peierls is a member of the JSR 166 Expert Group
Examples • PseudoRandom • Memoize • SwingWorker • BoundedBuffer • WebCrawler
Executors Executor ExecutorService ScheduledExecutorService Callable Future ScheduledFuture Delayed CompletionService ThreadPoolExecutor ScheduledThreadPoolExecutor AbstractExecutorService Executors FutureTask ExecutorCompletionService Queues BlockingQueue ConcurrentLinkedQueue LinkedBlockingQueue ArrayBlockingQueue SynchronousQueue PriorityBlockingQueue DelayQueue Concurrent Collections ConcurrentMap ConcurrentHashMap CopyOnWriteArray{List,Set} Synchronizers CountDownLatch Semaphore Exchanger CyclicBarrier Timing TimeUnit Locks Lock Condition ReadWriteLock AbstractQueuedSynchronizer LockSupport ReentrantLock ReentrantReadWriteLock Atomics Atomic[Type] Atomic[Type]Array Atomic[Type]FieldUpdater Atomic{Markable,Stampable}Reference Concurrency Utilities
Caveats • Access modifiers omitted unless relevant • InterruptedException abbreviated as IE • Most interrupt handling omitted for brevity, but very important in real life! • Irrelevant methods, fields, and arguments elided with “…”
PseudoRandom Example Overview • PseudoRandom: similar to java.util.Random • A simple but broken implementation • Reimplement PseudoRandom in different ways • Exercise the implementations • Compare their performance
PseudoRandom Example PseudoRandom: similar to java.util.Random interface PseudoRandom { /** * Return pseudorandom int in range [0, n) */ int nextInt(int n); }
PseudoRandom Example A simple but broken implementation class NaivePseudoRandom implements PseudoRandom { public int nextInt(int n) { int s = seed; seed = computeNext(seed); return s % n; } int computeNext(int s) { return …; } int seed = …; … }
PseudoRandom Example Fixed using synchronized class PseudoRandomUsingSynch implements PseudoRandom { public synchronized int nextInt(int n) { int s = seed; seed = computeNext(seed); return s % n; } int computeNext(int s) { return …; } int seed = …; }
PseudoRandom Example Better fix using Lock class PseudoRandomUsingLock implements PseudoRandom { public int nextInt(int n) { lock.lock(); try { int s = seed; seed = computeNext(seed); return s % n; } finally { lock.unlock(); } } int computeNext(int s) { return …; } int seed = …; final Lock lock = new ReentrantLock(true); }
Review: Lock Ensure consistency via exclusion • interface Lock { void lock(); void lockInterruptibly() throws IE; boolean tryLock(); boolean tryLock(long timeout, TimeUnit unit) throws IE; void unlock(); …} • class ReentrantLock implements Lock {ReentrantLock(boolean fair) {…}}
PseudoRandom Example Even better fix using AtomicInteger class PseudoRandomUsingAtomic implements PseudoRandom { public int nextInt(int n) { for (;;) { int s = seed.get(); int nexts = computeNext(s); if (seed.compareAndSet(s, nexts)) return s % n; } } int computeNext(int s) { return …; } final AtomicInteger seed = new AtomicInteger(…); }
Review: AtomicInteger Ensure consistency via atomic CAS • class AtomicInteger extends Number implements Serializable { • AtomicInteger(int initialCount) {…} boolean compareAndSet(int expect, int update) {…} boolean weakCompareAndSet(int expect, int update){…} • int get(); int getAndSet(int newValue) {…} int getAndAdd(int delta) {…} int addAndGet(int delta) {…} int getAndIncrement() {…} int getAndDecrement() {…} int incrementAndGet() {…} int decrementAndGet() {…} … }
PseudoRandom Example Exercise the implementations • DieRollTask repeatedly rolls a die • Coordinate tasks with CountDownLatch • Use Executor to run several DieRollTasks • Time multiple trials of each implementation • Test methodology disclaimer • Results
PseudoRandom Example Coordinate tasks with CountDownLatch final int NTASKS; // #instances of DieRollTask final int NROLLS; // #die rolls per task instance final PseudoRandom rnd; final CountDownLatch startSignal = new CountDownLatch(1); final CountDownLatch doneSignal = new CountDownLatch(NTASKS); class DieRollTask implements Runnable { public void run() { startSignal.await(); for (int i = 0; i < NROLLS; ++i) rnd.nextInt(6); doneSignal.countDown(); } /* IE handling omitted */ };
Review: CountDownLatch Coordinate thread activity • Waiting threads are released when count becomes 0 • class CountDownLatch {CountDownLatch(int initialCount) {…} void countDown(); void await() throws IE; void await(long timeout, TimeUnit unit) throws IE; long getCount(); }
PseudoRandom Example Use Executor to run several DieRollTasks final Executor threadPool = Executors.newFixedThreadPool(NTASKS); long trial(final int NROLLS, final PseudoRandom r) { … for (int j = 0; j < NTASKS; ++j) threadPool.execute(new DieRollTask()); …give tasks time to wait for startSignal… long start = System.nanoTime(); startSignal.countDown(); // release tasks doneSignal.await(); // wait ‘til done return System.nanoTime() – start; }
Review: Executor Decouple task submission from execution • Executor executes submitted tasks • interface Executor { void execute(Runnable task); } • Executors has static factory methods • class Executors { static ExecutorService // extends ExecutornewFixedThreadPool(int nThreads) {…}…many more… }
PseudoRandom Example Time multiple trials of each implementation interface PseudoRandomImpl { PseudoRandom newInstance(); } Collection<PseudoRandomImpl> impls; final int NTRIALS = …; // #trials/implementation final int NROLLS = …; // #die rolls/trial for (PseudoRandomImpl impl : impls) { long nanos = 0; PseudoRandom rnd; for (int k = 0; k < NTRIALS; ++k) { rnd = impl.newInstance(); nanos += trial(NROLLS, rnd); } long avg = nanos / (NTRIALS * NROLLS); if (rnd != null) report(rnd.getClass(), avg); }
PseudoRandom Example Test methodology disclaimer • One machine, one JVM, one platform • Other things that could affect results: • Packing of fields on objects • Cache locality of generated code • Thresholds for native compilation • Background user and system processes
PseudoRandom Example Results on 3.2 GHz Pentium 4, -client (Windows)
Memoize Example Implement "Memoizer" • Memo Function: A function that memorizes its previous results • Optimization for recursive functions, etc. • Invented by Prof. Donald Michie, Univ. of Edinburgh • Goal: Implement memoizer • Function wrapper • Provide concurrent access • Compute each result at most once • Materials: ConcurrentHashMap, FutureTask
Memoize Example Generic computation and example of use interface Computable<A, V> { V compute(A arg) throws Exception; } class Function implements Computable<String, BigInteger> { public BigInteger compute(String arg) { // after deep thought... return new BigInteger("2"); } } Function f = new Function(); // f = Memoize(f); f.compute("1 + 1"); >>> 2
Memoize Example Caching the computed result class Memoize1<A, V> implements Computable<A, V> { final Map<A, V> cache = new HashMap<A, V>(); final Computable<A, V> c; Memoize1(Computable<A, V> c) { this.c = c; } public synchronized V compute(A arg) throws... { if (!cache.containsKey(arg)) cache.put(arg, c.compute(arg)); return cache.get(arg); } } Computable<String, BigInteger> f; f = new Function(); f = new Memoize1<String, BigInteger>(f); f.compute("1 + 1"); >>> 2
Memoize Example No synchronization, but computes may overlap class Memoize2<A, V> implements Computable<A, V> { final Map<A, V> cache = new ConcurrentHashMap<A, V>(); final Computable<A, V> c; Memoize2(Computable<A, V> c) { this.c = c; } public V compute(A arg) throws Exception { if (!cache.containsKey(arg)) cache.put(arg, c.compute(arg)); return cache.get(arg); } }
Memoize Example Overlapping computes less likely class Memoize3<A, V> implements Computable<A, V> { final Map<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>(); … public V compute(final A arg) throws Exception { if (!cache.containsKey(arg)) { Callable<V> eval = new Callable<V>() { public V call() throws Exception { return c.compute(arg); } }; FutureTask<V> ft = new FutureTask<V>(eval); cache.put(arg, ft); ft.run(); } return cache.get(arg).get(); } }
Review: Callables and Futures Representing asynchronous tasks • Callable is invoked and returns a value • interface Callable<V> { V call() throws Exception; } • Future holds result of asynchronous computation • interface Future<V> { V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws IE, …; void cancel(boolean mayInterrupt); boolean isCancelled(); boolean isDone(); } • FutureTaskis a Runnable Future • class FutureTask<V>implements Future<V>, Runnable {FutureTask(Callable<V> c) {…} … }
Memoize Example Exactly one compute() for each argument value class Memoize<A, V> implements Computable<A, V> { final ConcurrentMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>(); final Computable<A, V> c; Memoize(Computable<A, V> c) { this.c = c; } public V compute(final A arg) throws Exception { Future<V> f = cache.get(arg); if (f == null) { Callable<V> eval = new Callable<V>() { public V call() throws Exception { return c.compute(arg); } }; FutureTask<V> ft = new FutureTask<V>(eval); f = cache.putIfAbsent(arg, ft); if (f == null) { f = ft; ft.run(); } } return f.get(); } }
Inside Memoize Exactly one compute() for each argument value Future<V> f = cache.get(arg); if (f == null) { Callable<V> eval = new Callable<V>() { public V call() throws Exception { return c.compute(arg); } }; FutureTask<V> ft = new FutureTask<V>(eval); f = cache.putIfAbsent(arg, ft); if (f == null) { f = ft; ft.run(); } } return f.get();
SwingWorker Example Reimplementation of SwingWorker • SwingWorker performs GUI-related work in a new thread • Created by Hans Muller • Goal: Reimplement using java.util.concurrent • Provide more features with less code • Materials: FutureTask, Executor
SwingWorker Revisited Perform GUI-related work in a new thread abstract class SwingWorker<V> { protected abstract V construct(); protected void finished() { } public void start(); public V get(); } SwingWorker<String> sw = new SwingWorker<String>() { protected String construct() { Thread.sleep(5000); return "Done"; } protected void finished() { label.setText(get()); } }; label.setText("Working..."); sw.start();
SwingWorker Revisited Working.. Done
SwingWorker Reimplemented abstract class SwingWorker<V> { FutureTask<V> task = new FutureTask<V>(new Callable<V>() { public V call() throws Exception { return construct(); } }) { protected void done() { EventQueue.invokeLater(new Runnable() { public void run() {finished(); } }); }; protected abstract V construct() throws Exception; protected void finished { } public void start() { new Thread(task).start(); } public V get() throws IE, ExecutionException { return task.get(); } }
Inside SwingWorker Calls construct, then invokes finished FutureTask<V> task = new FutureTask<V>(new Callable<V>() { public V call() throws Exception { return construct(); } }) { protected void done() {EventQueue.invokeLater(new Runnable() { public void run() {finished(); } }); };
SwingWorker Revisited Implements Future and Runnable abstract class SwingWorker<V> implements Future<V>, Runnable { … public void run() { task.run(); } public V get() throws IE, ExecutionException { return task.get(); } public V get(long timeout, TimeUnit unit) throws ... { return task.get(timeout, unit); } public boolean cancel(boolean mayInterruptIfRunning) ... public boolean isCancelled() ... public boolean isDone()... }
SwingWorker Revisited Pluggable Executor abstract class SwingWorker<V> ... { static final Executor EXECUTOR = new Executor() { public void execute(Runnable command) {new Thread(command).start(); } }; private Executorexecutor = EXECUTOR; public void setExecutor(Executor e) ... public Executor getExecutor() ... public void start() {executor.execute(this); } }
SwingWorker Revisited Executor choices • Parallel execution in cached thread pool • SwingWorker sw; • Executor e; • e = Executors.newCachedThreadPool(); • e.execute(sw); • Sequential execution on a single thread • e = Executors.newSingleThreadExecutor(); • e.execute(sw);
SwingWorker Example Summary • Concurrency utilities enable simple, powerful implementation • cancellation • get with timeout • pluggable executor • SwingWorker =Future + Callable + Runnable + Executor Note: Also see http://foxtrot.sourceforge.net
BoundedBuffer Example Overview • BoundedBuffer interface • Implement BoundedBuffer with Condition • Exercise the implementation • Compare with other implementations • Alternative approach for single producer / single consumer settings using Exchanger
BoundedBuffer Example BoundedBuffer interface interface BoundedBuffer<E> { /** * Adds element to buffer, blocking until * buffer not full */ void put(E element) throws InterruptedException; /** * Removes element from buffer, blocks until * buffer not empty */ E take() throws InterruptedException; }
BoundedBuffer Example Implement BoundedBuffer with Condition class BoundedBufferUsingCondition<E> implements BoundedBuffer<E> { finalLock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); E[] items; int putIndex, takeIndex, size; public void put(E element) throws IE { lock.lock(); try { while (size == items.length) notFull.await(); items[putIndex++] = element; ++size; if (putIndex == items.length) putIndex = 0; notEmpty.signal(); } finally { /* IE handling omitted */ lock.unlock(); } } // to be continued…
Review: Condition Multiple wait sets • Condition analogous to Object.wait/notify/notifyAll • interface Condition { void await() throws IE; void await(long timeout, TimeUnit unit) throws IE; void awaitUninterruptibly(); void signal(); void signalAll(); … } • Create Conditions with a Lock • interface Lock { … Condition newCondition(); }
BoundedBuffer Example Implement BoundedBuffer with Condition, cont’d // …continued: public E take() throws InterruptedException { lock.lock(); try { while (size == 0) notEmpty.await(); E element = items[takeIndex++]; --size; if (takeIndex == items.length) takeIndex = 0; notFull.signal(); } finally { /* IE handling omitted */ lock.unlock(); } } }
WebCrawler Example Overview • Application of producer/consumer pattern • Implemented using thread pool • Problem: Implementation blocks on fetch • Solution: Pool runs each fetch as task • Wrinkle: Need concurrent collections • Wrinkle: Denial of service • Resolution: Schedule periodic fetches • Refinements
WebCrawler Example Application of producer/consumer pattern WebCrawler webCrawler = …; BlockingQueue<URL> rq = …; // result queue Future<?> crawl = webCrawler.crawl(startUrl, rq); try { while (!foundEnough()) { URL found = rq.poll(timeout, unit); if (found == null) break; process(found); } } finally { crawl.cancel(true); }
Review: BlockingQueue Blocking version of java.util.Queue interface BlockingQueue<E> extends Queue<E> { boolean add(E e); boolean offer(E e); boolean offer(E e, long timeout, TimeUnit unit) throws IE; E poll(long timeout, TimeUnit unit) throws IE; • void put(E e) throws IE; E take() throws IE; • int remainingCapacity(); • int drainTo(Collection<? super E> elements); int drainTo(Collection<? Super E> elements, int maxElements); }
WebCrawler Example Implemented using cached thread pool class WebCrawler { final ExecutorService pool = Executors.newCachedThreadPool(); Future<?> crawl(URL startUrl, BlockingQueue<URL> rq) { return pool.submit(new CrawlTask(startUrl, rq)); } class CrawlTask implements Runnable { /** * Gets URL contents and parses list of URL links * from them; may block indefinitely. */ List<URL> fetchLinks(URL url) throws IOException {…} … } }
WebCrawler Example Problem: Implementation blocks on fetch class CrawlTask implements Runnable { public void run() { for (seen.add(url); !done(); url = poll()) try { for (URL link : fetchLinks(url)) add(link); if (!rq.offer(url, timeout, unit)) return; } catch (IOException e) { // skip over url, couldn’t get its contents } } void add(URL u) { if (!seen.contains(u)) { seen.add(u); pq.offer(u); } } boolean done() { return url == null; } URL poll() { return pq.poll(); } URL url = startUrl; final Set<URL> seen = new HashSet<URL>(); final Queue<URL> pq = new PriorityQueue<URL>(…, searchOrder); }
WebCrawler Example Solution: Pool runs each fetch as task public void run() { for (seen.put(url, true); !done(); url = poll()) pool.execute(new Runnable() { public void run() { try { for (URL link : fetchLinks(url)) add(link); if (!rq.offer(url, timeout, unit)) { done = true; return; } } catch (IOException e) { /* skip */ } } }); } boolean done() { return done || url == null; } volatile boolean done = false; …continued on next slide…