L6: Threads “the future is parallel” COMP206, Geoff Holmes and Bernhard Pfahringer
Multi-threading Multi-tasking: do multiple things in parallel: –Edit your game –Surf the web –Listen to mp3s –… –Also inside single App: mp3 player (decompress, play, graphics) but usually only 1 or 2 CPUs with 1 or 2 cores that switches between tasks Multi-threading similar, but inside a single program/process/context share data directly, same classes/methods/variables
Creating threads: 2 ways Subclassing Class Thread class ExpensiveComputation extends Thread –Must implement run() method –Use start() on a new instance: (new ExpensiveComputation()).start(); Implement interface Runnable: class ExpensiveComputation implements Runnable –Must implement run() method –Start by wrapping inside a Thread instance: Thread thread = new Thread(new ExpensiveComputation ()); thread.start(); Both will stop automatically, once run() finishes
Scheduling Can be pre-emptive or cooperative, Each Thread should regularly call one of yield() wait() sleep() to give other threads a chance to run as well All event handlers (all listeners) and repaint() execute in the same event-handling thread: methods like “actionPerformed()” should be fast, for more substantial computations they should: Start a new thread Somehow tell another thread what to do Otherwise screen repainting will suffer!
Synchronization If two (or more) threads access some value simultaneously, and at least one wants to modify the value, things can go wrong: a += 10; Thread1: (a1) read a, (b1) compute a+10, (c1) write a Thread2: (a2) read a, (b2) compute a+10, (c2) write a What is the value of a after the following sequence: a1,b1,a2,b2,c2,c1
Synchronized methods public synchronized void increment() { a += 10; } Only one thread at a time can execute this method on the same instance no interleaving possible (“atomic action”) no inconsistencies but: beware of inefficiencies/deadlocks
Higher level: java.util.concurrent Lots of useful high-level stuff for concurrency: E.g. Thread pools: –Threads use quite some resources (especially memory) –Pools limit the number of threads, queue requests for more threads/computation –Degrade more gracefully under high load
Thread pools java.util.concurrent.Executors. newFixedThreadPool(int poolSize) creates an ExecutorService of n threadsjava.util.concurrent.Executors. Good default: poolSize = number of cores submit(Runnable task) returns a Futuresubmit Must explicitly shutdown() ExecutorService
Runnable / Callable / Future Future is a “Wrapper” representing the future value of the thread’s computation Access with get(), will block until thread is finished => easy synchronisation Runnable’s run() method returns only void Callable’s call() method can return any Object, but does not take arguments => use constructor to “pass in” arguments
Code example int poolSize = 4; ExecutorService pool = Executors.newFixedThreadPool(poolSize); List > results = new ArrayList >(); for (int i = 0; i < max; i++) { results.add(pool.submit(new SomeComputation(i))); } // wait until all are finished and collect results: double total = 0.0; for (Future f: results) total += f.get(); pool.shutdown();
Cont. public class SomeComputation implements Callable { private int offset = -1; public SomeComputation(int offset) { this.offset = offset; } public Double call() { // some computation using “offset” // safe “atomic” output synchronized (System.out) { System.out.println(”someComputation offset = “ + offset + “ “ + someMessage); } return someResult; }
java.util.concurrent.atomic Thread-safe “atomic” access for certain objects and references E.g. AtomicInteger int addAndGet(delta)addAndGet Atomically adds the given value to the current value and returns result. [see also concurrent versions of collections] [synchronization can be expensive, can lead to deadlocks and similar, avoid, or replace with high-level constructs]