1 / 67

Concurrency Utilities in Practice

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

ling
Download Presentation

Concurrency Utilities in Practice

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Concurrency Utilities in Practice Using java.util.concurrent Tim Peierls Prior Artisans, LLC

  2. Goals • Review concurrency utilities • Present several applications • Measure and evaluate performance

  3. Speakers Tim Peierls is a member of the JSR 166 Expert Group

  4. Examples • PseudoRandom • Memoize • SwingWorker • BoundedBuffer • WebCrawler

  5. 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

  6. 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 “…”

  7. 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

  8. PseudoRandom Example PseudoRandom: similar to java.util.Random interface PseudoRandom { /** * Return pseudorandom int in range [0, n) */ int nextInt(int n); }

  9. 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 = …; … }

  10. 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 = …; }

  11. 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); }

  12. 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) {…}}

  13. 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(…); }

  14. 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() {…} … }

  15. 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

  16. 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 */ };

  17. 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(); }

  18. 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; }

  19. 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… }

  20. 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); }

  21. 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

  22. PseudoRandom Example Results on 3.2 GHz Pentium 4, -client (Windows)

  23. 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

  24. 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

  25. 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

  26. 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); } }

  27. 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(); } }

  28. 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) {…}   … }

  29. 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(); } }

  30. 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();

  31. 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

  32. 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();

  33. SwingWorker Revisited Working.. Done

  34. 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();   } }

  35. 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();        }    }); };

  36. 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()... }

  37. 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); } }

  38. 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);

  39. 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

  40. 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

  41. 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; }

  42. 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…

  43. 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(); }

  44. 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(); } } }

  45. 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

  46. 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); }

  47. 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); }

  48. 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 {…} … } }

  49. 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); }

  50. 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…

More Related