Intro to multi-threaded programming in Java jonas.kvarnstrom@liu.se – 2015 2 Most operating systems allow multitasking Multiple tasks are run in parallel (given enough processor cores) ▪ Word processor, file manager, web browser ▪ Background MP3 player, virus checker ▪ … Tasks are ”separate programs” with their own code, data, stack Process 1 Process 2 No access Program Memory heap Program Memory heap jonkv@ida Intro 1: Multitasking 3 Multi-threading (lightweight processes) A ▪ ▪ ▪ single program can have multiple threads of execution Executed in parallel (given enough CPU cores) Same heap, where objects are stored Different stack, keeping track local variables + method calls … Word Processor Web Server No access GUI Thread Print thread Background spell checker Shared heap Request handler Request handler Request handler Shared heap jonkv@ida Intro 2: Threads 4 Java is fundamentally multi-threaded Create threads using the Thread class Manage access to resources using the synchronized keyword Handle communication between threads using wait(), notify() methods Threads for a standard non-GUI program (Java 7, Windows): ▪ Group: system Max Priority: 10 Reference Handler [pri 10, Daemon] -- Handles weak references Finalizer [pri 8, Daemon] -- Runs finalizers Signal Dispatcher [pri 9, Daemon] Daemon: Background services, Attach Listener [pri 5, Daemon] won’t prevent the program from Group: main Max Priority: 10 terminating main [pri 5] -- The thread that calls main() Additional Threads for a Swing GUI program (Java 7, Windows): ▪ Java2D Disposer [pri 10, Daemon] AWT-Windows [pri 6, Daemon] jonkv@ida Intro 3: Threads and Java 5 To create a thread, first define what it should do The object-oriented way: An object with a specific method to run ▪ public class DocSaver implements Runnable { … public void run() { Runnable interface: … save the document to disk … A single method } } Second step: Create an instance of your Runnable class ▪ final Runnable saver = new DocSaver(); Third step: Create a Thread that will execute the Runnable ▪ final Thread t1 = new Thread(saver, "Document Saver"); Gives a name to the thread Fourth step: Start the thread! ▪ t1.start(); Creates an operating system thread with stack, etc. jonkv@ida Intro 4: Creating Threads 6 Threads can be in one of four states: New – created with new Thread(…), not yet started Runnable – has or wants the CPU ▪ The scheduler determines which runnable thread(s) will run ▪ Time allocation depends on thread priority Blocked – waiting for something to happen ▪ Blocking on I/O – waits for I/O to complete ▪ Called sleep() – waits n milliseconds ▪ Called obj.wait() – waits until notified Dead – stopped running ▪ The Runnable's run() method has returned ▪ But the Thread object still exists (not yet garbage collected) Thread. start() Done waiting Returns to runnable jonkv@ida Intro 5: Thread States 7 Threads often share resources Document saving thread Spell checker thread Document editing thread Swing thread (events, painting) Document object What if a document changes while it is being saved? Requires support for coordination, mutual exclusion between threads, … jonkv@ida Topic 1: Sharing Resources 8 Threads sometimes cooperate to achieve a common purpose Image Processing on core 1 Coordinating thread in image processing software Image Processing on core 2 Image Processing on core 3 Image Processing on core 4 How to communicate results? Requires support for communication between threads jonkv@ida Topic 2: Cooperation When threads cooperate… Develop a multi-threaded queue (producer/consumer) Potential delays Producer thread: Read data from disk, append to queue Consumer thread: Read data from queue, play a sound 10 jonkv@ida Communication 1: Producer/Consumer 11 Read data from disk, append to queue class Producer implements Runnable { private final MsgQueue queue; public Producer(MsgQueue q) { this.queue = q; } public void run() { Read from queue, decode MP3, play sound while (true) { Msg data = [[read]]; class Consumer implements Runnable { queue.put(data); private final MsgQueue queue; } public Consumer(MsgQueue q) { this.queue = q; } } public void run() { } while (true) { Msg data = queue.take(); [[play sound from data]]; } } } jonkv@ida Communication 2: Producer/Consumer 12 First attempt: Let's use an ArrayList! public class MsgQueue { } List<Msg> list = new ArrayList<Msg>(); public synchronized void put(Msg msg) { list.add(msg); // At the end Problem! } public synchronized Msg take() { If the queue is empty, return list.remove(0); there is no element to pop } ArrayIndexOutOfBoundsException Solution! The consumer must wait for an element to arrive from the producer! Java provides obj.wait() and obj.notify() for thread communication – tricky! jonkv@ida Communication 3: First Attempt 13 Fortunately, Java already provides concurrent queues! Usually don't need to use the low-level methods ▪ Easy to make mistakes… interface BlockingQueue<E> extends Collection<E>: ▪ Supports blocking and timeouts What happens if the operation can’t be performed (now)? Throw exception Return null or false Block (wait until the operation can be performed) Time out (after a specific interval) Insert (queue may be full) add(e) offer(e) put(e) offer(e, time, unit) Remove (may be empty) remove() poll() take() poll(time, unit) Examine (may be empty) element() peek() not applicable not applicable jonkv@ida Communication 4: Concurrent Queues 14 Implemented by ▪ ▪ ▪ ▪ ▪ ▪ ▪ ArrayBlockingQueue SynchronousQueue LinkedBlockingQueue LinkedBlockingDeque DelayQueue PriorityBlockingQueue LinkedTransferQueue – bounded size – zero capacity, each insert matched with a remove – – – each element is available when its delay expires – – where producers may wait for consumers jonkv@ida Concurrent (Blocking) Queues Shared Resources When is a Program Thread Safe? Mutual Exclusion: Conceptual View and Java Syntax Threads accessing the same data must not interfere! Simple example: ArrayList class used from multiple threads ▪ public class ArrayList<E> { private int size = 0; private E[] elements = (E[]) new Object[100]; public void add(E obj) { elements[size] = obj; size++; } public void clear() { elements = (E[]) new Object[100]; size = 0; } } 16 jonkv@ida Thread Safety 1: Intro With a single CPU core… The computer quickly switches between active threads (time slicing) This happens even inside methods! Timer Swing 17 With multiple CPU cores… Multiple threads can be running truly simultaneously Timer Swing begin update() begin repaint() begin update() begin repaint() continue continue continue continue jonkv@ida Thread Safety 2: Thread Switching 18 jonkv@ida Thread Safety 3: Within Methods What happens within a method? Java source (text): Hello.java Compiler: javac Hello.java class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); … … … … } } Bytecode (binary intermediate format): Hello.class Standardized, can be distributed Method void main(java.lang.String[]) b2 00 0b getstatic #12 <Field java.io.PrintS 12 0f ldc #15 <String "Hello, world"> b6 00 14 invokevirtual #20 <Method void 03 iconst_0 3c istore_1 a7 00 27 goto 39 b2 00 0b getstatic #12 <Field java.io.PrintS bb 00 17 new #23 <Class java.lang.StringBu 19 Even with one core: Can switch threads after any atomic (indivisible) bytecode operation Very fine-grained! Thread 2 executing a[i] = j push a onto stack push i onto stack push j onto stack arraystore store value at top of stack in size Bytecode instructions Thread 1 executing size++ push size value onto stack increment top of stack jonkv@ida Thread Safety 4: Fine-Grained Switching 20 jonkv@ida Thread Safety 5: Race condition! Suppose two threads call add(A) / add(B) concurrently public void add(E obj) { elements[size] = obj; size++; } ▪ Might expect: [A,B] or [B,A]. This is not guaranteed! ▪ Called a race condition: Depends on "who gets there first" (exactly how far does one thread get before it is interrupted?) read size: 0 store A at [0] read size: 0 inc 1 store size: 1 read size: 1 store B at [1] read size: 1 inc 2 [A,B] store size: 2 read size: 0 store A at [0] read size: 0 read size: 0 store B at [0] read size: 0 inc 1 store size: 1 inc 1 [B] store size: 1 read size: 0 store A at [0] read size: 0 store B at [0] read size: 0 inc 1 store size: 1 read size: 1 inc 2 store size: 2 [B,null] 21 One common requirement for thread safety: Parallel execution should be equivalent to some serialization ▪ The result should be as if the concurrent calls from multiple threads had been made in some sequence from a single thread ▪ Only the result counts – if you can still do it concurrently, it's better! If you do this… (multiple CPUs) or this… (one CPU) The result should be as if you did this… or as if you did this. add(a) add(a) add(b) add(b) add(a) add(b) add(b) add(a) jonkv@ida Thread Safety 6: One Requirement 22 How is this type of thread safety achieved? Sometimes through very clever algorithms and data structures ▪ ConcurrentLinkedQueue – threadsafe linked queue ▪ ConcurrentLinkedDeque – threadsafe linked double-ended queue ▪ ConcurrentHashMap – efficient multithreaded access without locking ▪ ConcurrentSkipListMap – another variation Methods do execute like this… …or like this… add(a) add(a) add(b) add(b) ...but regardless of scheduling, correct results are guaranteed! Beyond the scope of this course! jonkv@ida Thread Safety 7: Cleverness 23 How is this type of thread safety achieved? Usually through mechanisms for mutual exclusion Can’t add() to the same list from two threads concurrently Make sure you can’t do this… … or this … One thread will have to wait for the first add() to complete You can only do this… …or this. add(a) add(a) add(b) add(b) add(b) add(a) add(b) add(a) jonkv@ida Thread Safety 8: Mutexes 24 One mechanism for mutual exclusion: Semaphore / monitor semaphore.acquire() ▪ Waits until no other thread has the semaphore, then "locks" it for this thread semaphore.release() ▪ Releases the semaphore, so that some other thread can use it list.add(A): list.semaphore.acquire(); … list.semaphore.release(); list.add(B): list.semaphore.acquire(); … list.semaphore.release(); These functions use specific low-level machine instructions to avoid race conditions This allows you to use the semaphore at a higher level of abstraction… jonkv@ida Mutex 1: Semaphore/Monitor Mutual exclusion and semaphores / monitors in Java: Every object is associated with a monitor Acquired and released through synchronized blocks: ▪ synchronized(myObject) { // Acquire() myObject’s monitor … } // Implicitly release() it again Synchronized blocks provide structure to monitors ▪ Automatically release()d when you exit the block: You can't forget to release them, even if an exception terminates the method 25 jonkv@ida Mutex 2: Monitors in Java Must use a monitor in the list example Which one? Create a new object… ▪ public class ArrayList<E> { private int size = 0; private E[] elements = (E[]) new Object[100]; private Object monitor = new Object(); public void add(E obj) { synchronized(monitor) { // acquire monitor elements[size] = obj; size++; } // release monitor } public void clear() { synchronized(monitor) { elements = (E[]) new Object[100]; size = 0; } } } 26 jonkv@ida Mutex 3: Monitors in ArrayList 27 Suppose thread A calls add(A) First: synchronized(monitor) acquires the list’s monitor Conceptual view – the monitor is not a Java object with fields! ▪ Atomic test-and-set: acquired is false, so make it true ▪ Make thread A the owner of the monitor ▪ before synchronized(mon) { … } acquired false owner lockqueue … … Thread object: Thread A, runnable A: acquire A: read size 0 A: store A at 0 … within synchronized(mon) { …} acquired true owner lockqueue … … Thread A runnable jonkv@ida Mutex 4: Acquiring a Monitor 28 … A: read size 0 B: try acquire B: block [other threads might run here] If the scheduler lets B run, before A is done: Method add() calls synchronized(mon) ▪ Tries to acquire the monitor ▪ Atomic test-and-set: acquired is true, so we fail ▪ Thread B is blocked, placed in a wait queue! When thread B is blocked: ▪ The scheduler selects another runnable thread (not necessarily A!) acquired true owner lockqueue … … Thread A runnable Thread B blocked Thread C runnable Thread D runnable jonkv@ida Mutex 5: Wait Queue 29 Eventually, thread A is scheduled to run again ▪ Finishes its work (no interference!) and releases the monitor ▪ Thread B is unblocked (runnable), but thread A continues running acquired false owner lockqueue … … Thread A runnable Thread B runnable Eventually, thread B is scheduled to run again ▪ Thread B is allowed to acquire the monitor acquired true owner lockqueue … … Thread B runnable … A: read size 0 B: try acquire B: block [other threads run] A: inc 1 A: store size 1 A: release A: keep working jonkv@ida Mutex 6: Wait Queue 30 Be careful: Mutual exclusion is cooperative Not like placing the object in a safe… More like this: ArraySet obj. ArraySet obj. header header elements: [] size: 0 elements: [] size: 0 ▪ public void getSize() { return size; } ▪ No synchronized() block can be called in parallel with add() or any other method! Fields can be accessed Methods can be called Everyone must cooperate by checking the note (trying to acquire the monitor)! jonkv@ida Mutex 7: Cooperative Must all methods use synchronization? Depends on your class… ▪ // No synchronized() can be called in parallel with other methods public void getSize() { return size; } ▪ // Calling unsynchronized getSize() in parallel with clear1() // returns either the old size, or the new size 0 – OK! public void clear1() { synchronized(monitor) { elements = (E[]) new Object[100]; size = 0; } } ▪ // Calling unsynchronized getSize() in parallel with clear2() // may return an "intermediate" size, that the list never really had! // (One solution: synchronization in getSize() too) public void clear2() { synchronized(monitor) { while (size > 0) elements[--size] = null; } } 31 jonkv@ida Mutex 8: All Methods Synchronized? How to ensure thread safety, if mutexes are cooperative? Anyone can just skip synchronization, deliberately or by mistake… Use standard public/private protection! Make sure all fields are private ▪ Public fields can be accessed regardless of synchronization ▪ Other threads can read intermediate values of public fields ▪ Other threads can change values while mutators are being executed ▪ But private fields are only accessed within the class – which you control! Make sure all methods use synchronization where necessary 32 jonkv@ida Mutex 9: Cooperative Protection How do you ensure complete thread safety? The same way you write a bug-free program… ▪ Think! ▪ Consider all possible combinations of circumstances! ▪ Do everything right! Requires policy / design decisions, which must be followed Complicated because high parallelism is desired ▪ That is the reason why we use threads in the first place ▪ Don't want too much mutual exclusion Sorry, but there are no simple answers! 33 jonkv@ida Conclusions: Complete Thread Safety Synchronization has some overhead! 35 Without synchronized: 651 ms 571 ms 566 ms 553 ms 548 ms ▪ public class SynchTester { public static void main(String[] args) { for (int i = 0; i < 5; i++) doIt(); } private static void doIt() { With synchronized: SynchTester tester = new SynchTester(); 20175 ms long start = System.currentTimeMillis(); Huge 1368 ms int value = 1; proportional diff: 539 ms for (int i = 0; i < 1_000_000_000L; i++) { The method is 545 ms value = tester.increment(value); trivial in itself 545 ms } System.out.println((System.currentTimeMillis()-start) + "ms: " + value); } public synchronized int increment(int var) { In this case, the method is return var + 1; dynamically optimized to } remove the overhead. } Not always possible! jonkv@ida Synchronization Overhead Collection classes: Often used in single-threaded context Three alternatives: Don’t use synchronization public void add(Object obj) { // Lots of code adding an element } If you use from multiple threads: Risk incorrect results (lost elements, …) Do use synchronization public void add(Object obj) { synchronized(monitor) { // Code adding an element } } Unnecessary overhead in most cases Provide both versions! A lot of redundant code… right? 37 jonkv@ida Synchronization and Collections Solution: Do provide two versions, don’t write collections twice Don’t use synchronization 38 public void add(Object obj) { // Lots of code adding an element } Do use synchronization public synchronized void add(Object obj) { // Quick call to the “real” class: // Delegation } jonkv@ida Synchronization and Collections (2) 39 To achieve this: Use a synchronization wrapper public class SynchList implements List { private List storage; private Object monitor = …; public SynchList(List storage) public void add(Object obj) { synchronized(monitor) } Implements List can be used ”everywhere” { this.storage = storage; } { storage.add(obj); } } synchList ArrayList "SynchList" Object header: storage (data) Object header: size elements (data) 0 Uses the decorator design pattern Header (data) length [0] [1] [2] [3] 4 jonkv@ida Collections: Synchronization Wrappers Synchronization wrappers exist in the Collections framework! List<String> synchList = Collections.synchronizedList(new ArrayList<String>()); … 40 jonkv@ida Built-in Synchronization Wrappers 42 To schedule arbitrary tasks: java.util.Timer Starts a separate thread for each Timer – not dependent on Swing timers ▪ import java.util.Timer; final TimerTask task = new TimerTask() { Don’t manipulate public void run() { System.out.println(“Running!"); } an existing GUI }; here: ▪ Timer t = new Timer(); // Schedule a task to run a single time, after 500 ms t.schedule(task, 500); Only permitted in the Swing thread! ▪ // Schedule a task to run repeatedly, starting now, // 500 ms from execution n ends to execution n+1 begins t.schedule(task, new Date(), 500); ▪ // Schedule a task to run repeatedly, starting now, // 500 ms from execution n begins to execution n+1 begins t.scheduleAtFixedRate(task, new Date(), 500); … jonkv@ida Timers