- Concurrency (cont.) Today: Concurrency in Java Consistency issues on multiprocessors First, monitors - start with locks, as before - add "condition variables" (in Java, they're the same thing) Condition variables - supported calls: wait(), notify(), notifyAll() - one thread calls wait() - implicitly, all locks being held are freed - another thread calls notify() - places exactly one wait()ing thread back on the run queue - locks implicitly reacquired in original order - notifyAll() places *all* waiting thread back on run queue How all this works in Java - Every object has a lock built into it - Every class has one as well - synchronized keyword on method -- acquires instance lock - synchronized keyword on static -- acquires class lock - can add new locks (just get yourself a new Object) - synchronized(any_object) { code block } - How to create a new thread? - starting a thread: childThread = new Thread(new Runnable { public void run() { ... } }); childThread.start(); - waiting for a thread to die: childThread.join(); - yielding Thread.currentThread().yield(); - Footnote: early versions of Java used non-pre-emptive threads, but current versions all have pre-emption. yield() is a way to be polite, but it's not required. - Last time, remember we had read-locks and write-locks? - Java style is typically to just use the class lock - wait() when somebody's going to change the state - notifyAll() when a change is made - put wait() into an infinite loop - implementing a producer/consumer style buffer class Buffer { private Object storage; synchronized void store(Object x) { while(storage != null) wait(); storage = x; notifyAll(); } synchronized Object get() { while(storage == null) wait(); Object tmp = storage; storage = null; notifyAll(); return tmp; } } - exercises for the reader - increasing the storage space (allows for more concurrency) - separate read and write locks (allows for fewer people waking up each time) - Ugly issue: write consistency - Digression: how memory works in multi-processor machines - Cache hierarchy - Delayed write propagation to RAM - Write ordering, etc. - Java spec says: - No guarantees without locks - Anything or nothing could happen - After *any* lock is released, *all* changes propagate immediately - Note: *many* different kinds of consistency models (different on different kinds of multiprocessors, distributed clusters, etc.) - Paranoia: use locks and you'll be fine - A concrete problem: implementing a "Stop" button - use "volatile" boolean - "volatile" guarantees that write are immediately visible everywhere - CPU-thread is doing some "real" work - GUI-thread receives button click, sets boolean to true - CPU-thread occasionally reads boolean, sets to false ==> no locking required ========== ========== ========== ========== ========== ========== ========== ========== Newer features of Java 1.5 java.util.concurrency.* classes java.util.concurrent.atomic - lock-free, thread-safe programming on single variables (AtomicBoolean, AtomicLong, etc.) - methods like boolean compareAndSet(expectedValue, updateValue) - works just like "test and set" instructions that are native in many CPUs - sets the internal value to the updateValue, only if the current value equals the expected value - returns 'true' if it all worked - other fun methods like getAndIncrement() java.util.concurrent.locks - a couple new things that should have been in Java from day zero - class ReentrantLock adds a few useful features - query if the lock is held by anybody - acquire a lock but allow yourself to be interrupted by another thread (Thread.interrupt()) - Condition variables are now separate from locks (see newCondition() method on ReentrantLock) - makes it easier to have a read-condition and a write-condition on a buffer, for example - await() methods much like Object.wait() - signal() much like Object.notify() - signalAll() much like Object.notifyAll() - ReentrantReadWriteLock - Even fancier, with support for multiple-reader single-writer semantics. Notice how these library locks, because they're not built into the language, require you to be very careful about exception handling? This happens "for free" when you're using regular Java locks in synchronized blocks. class RWDictionary { private final Map m = new TreeMap(); private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); public Data get(String key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } public String[] allKeys() { r.lock(); try { return m.keySet().toArray(); } finally { r.unlock(); } } public Data put(String key, Data value) { w.lock(); try { return m.put(key, value); } finally { w.unlock(); } } public void clear() { w.lock(); try { m.clear(); } finally { w.unlock(); } } } - See Sun's other example below (note how they acquire the read-lock before releasing the write-lock) class CachedData { Object data; volatile boolean cacheValid; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // upgrade lock manually rwl.readLock().unlock(); // must unlock first to obtain writelock rwl.writeLock().lock(); if (!cacheValid) { // recheck data = ... cacheValid = true; } // downgrade lock rwl.readLock().lock(); // reacquire read without giving up write lock rwl.writeLock().unlock(); // unlock write, still hold read } use(data); rwl.readLock().unlock(); } } - Material that will probably end up in our "concurrency 3" lecture ========== ========== ========== ========== ========== ========== ========== ========== Fancy classes in java.util.concurrent to make your life easier, so you don't have to worry about getting these sorts of things correct: - java.util.concurrent.CyclicBarrier - implements a concept called "barrier synchronization", used commonly when you want to have N worker threads, each doing a partition of a bigger problem, but they all need to finish up before you can use the result. int N=10; CyclicBarrier cb = new CyclicBarrier(N+1); // N+1, to include the master thread class Worker { public Worker(int i) { // do actual work on the i'th row cb.await(); } } for(int i=0; i