MultiThreading Programming Object-Oriented Technology and Development
Thread Objectives Understand basic concurrency terminology Create active Java objects using the Runnable interface and Thread class Write Java objects that are thread safe Synchronized methods and object locking Understand concurrency concepts including thread groups, liveness vs. safety, and nondeterminism Know tradeoffs between JVM thread implementations Understand periodic and aperiodic tasks and how to use the Java Timer to perform scheduling
Concurrency Terminology Simple Process executes until it surrenders CPU Blocking operations (I/O) may force surrender Time-slicing Process executes for period of time, then surrenders CPU Priority Processes have priorities, higher executes before lower Preemption Process with higher priority will interrupt lower Round-robin A process will not execute until all others have executed Commonly used with priorities
Threads Operating systems provide concurrent processes Each process contains its own data and stack Threads are processes within a single OS process Sometimes called "lightweight processes" Threads share the data of the parent processes Object created by one thread is visible to another Threads have their own stack for local variables
Thread Example class Proc extends Thread { protected int id; public Proc (int assignedID) { id = assignedID; } public void run() { for (int loop=0; loop < 5; loop++) System.out.println("Hello from " + id + " loop=" + loop); class Main { public static void main(String args[]) { int times= (new Integer(args[0])).intValue(); for (int loop=0; loop < times; loop++) { Thread p = new Proc(loop); p.start();
Thread Example Output Threads may execute in any order Hello from 0 loop=0 Hello from 1 loop=0 Hello from 2 loop=0 Hello from 2 loop=1 Hello from 0 loop=1 Hello from 0 loop=2 Hello from 0 loop=3 Hello from 1 loop=1 Hello from 1 loop=2 Hello from 1 loop=3 Hello from 2 loop=2 Hello from 2 loop=3 Hello from 0 loop=4 Hello from 2 loop=4 Hello from 1 loop=4 Threads may execute in any order
Class Thread: An Overview of the Thread Methods Class Thread constructors public Thread( String threadName ) public Thread() Code for thread in thread’s run method Method sleep makes thread inactive Method interrupt interrupts a running thread Method isAlive checks status of a thread Method setName sets a thread’s name Method join Waits for thread to finish and continues from current thread
Threaded States: Life Cycle of a Thread Thread states Born state Thread was just created Ready state Thread’s start method invoked Thread can now execute Running state Thread is assigned a processor and running Dead state Thread has completed or exited Eventually disposed of by system
Thread States: Life Cycle of a Thread State diagram showing the Life cycle of a thread.
Creating Java Threads Two mechanisms for creating active Java objects 1) Derive from class Thread 2) Implement Runnable interface Runnable defines a protocol for active objects Contains a single method "void run()" Thread defines a thread implementation Thread implements Runnable
Thread Properties Thread Methods Thread States run() code to execute for thread start() called by client to begin thread's execution sleep(long) suspends thread for # milliseconds yield() forces scheduling to occur Thread States new after new is called on a threaded class creates thread, but does not begin execution runnable after start() is called (blocked, sleep, suspend, wait) dead completed
Thread Priorities and Thread Scheduling Priorities defined between one and ten (high) Five is default Scheduling is preemptive, round-robin Time Slicing Not guaranteed (Solaris: no, Windows 95/NT: yes) Threads without blocking calls (I/O) can run forever User should call yield() to be kind to other threads Typically, programmer not concerned with scheduling
Shared Data Access Objects are visible to multiple threads What if two threads access data simultaneously? ActiveObject1 executes a method on SharedObject Before method completion, ActiveObject2 calls the method Potential "race condition" SharedObject's state may become inconsistent :ActiveObject1 :SharedObject :ActiveObject2
Monitors Solution: turn a Java class into a monitor Monitor semantics Monitors are a synchronization mechanism defined by Hoare (Communicating Sequential Processes) Monitor semantics Clients must gain a lock on the monitor object before invoking methods Clients waiting for the lock are placed in a queue A client with a lock may surrender the lock without "leaving" the object May later resume execution after re-obtaining the lock
Monitors in Java Synchronized methods create monitors class Account { public synchronized deposit (Account a, int amount); public synchronized withdraw (Account a, int amount); public int balance(); } Synchronized methods lock the object during their execution Objects with synchronized methods have a queue of waiting threads Static methods need only obtain the class lock Non-static methods must lock the object and the class
Synchronization Example :Proc :Data :Proc access :Proc class Synchronization { public static void main(String args[]) { Data theDataItem = new Data(); for (int i=1; i <= 3; i++){ Thread p = new Proc(i, theDataItem); p.start(); } Creating 3 threads
Synchronization Example: Thread class Proc extends Thread { private int id; private Data theData; public Proc(int x, Data d) { id = x; theData = d; } public void run() { System.out.println("Thread started #" + id); try { for (int i = 0; i < 3; i++) { theData.access(id, i); yield(); catch (Exception e) {e.printStackTrace();}
Synchronization Example: Data class Data { synchronized public void access(int who, int count) { System.out.println(this + " data object accessed by " + who + " count is " + count); }
Example Outputs Thread started #1 Thread started #3 Data@1cc738 data object accessed by 1 count is 0 Thread started #2 Data@1cc738 data object accessed by 3 count is 0 Data@1cc738 data object accessed by 3 count is 1 Data@1cc738 data object accessed by 2 count is 0 Data@1cc738 data object accessed by 2 count is 1 Data@1cc738 data object accessed by 1 count is 1 Data@1cc738 data object accessed by 1 count is 2 Data@1cc738 data object accessed by 3 count is 2 Data@1cc738 data object accessed by 2 count is 2
Guarded Entries Object may not be able to grant request Do not want client to retry operation which is analogous to polling and too inefficient Solution is a monitor with guarded entries protected type bank function balance; procedure deposit; //signals state change procedure withdraw (amount) [when (amount <= balance)] end bank; :Bank :Client1 balance deposit :Client2 withdraw
Waiting Example Java implements guarded monitors with wait()/notify() class Bank { private float balance; synchronized void withdraw (float amount) { try { while (amount > balance) wait(); } catch(InterruptedException e) { e.printStackTrace(); } balance = balance - amount; synchronized void deposit (float amount) { balance = balance + amount; notify(); float balance() { return balance; }
Explicit Locking Java provides two mechanism to lock an object Synchronized methods on the object's class A synchronized block synchronized (<object>) { // obtain lock on <object> <object>.changeState(); } Synchronized blocks obtain the object's lock Responsibility is placed on the caller, not the object implementation Potential source for errors in concurrent program
The Runnable Interface Java's second mechanism to implement concurrency Not all classes can extend thread (may already be a subclass) Any class can implement Runnable Must provide a run() method Instances are not by default concurrent threads Must create a Thread instance
Runnable Example class Proc implements Runnable { <same as before> } class Main { public static void main(String args[]) { int times = (new Integer(args[0])).intValue(); for (int loop=0; loop < times; loop++) { Runnable runner = new Proc(loop); Thread proc = new Thread(runner); proc.start();
Thread Groups Coordinate startup and shutdown of threads Want to initialize all threads before beginning their execution Increase performance by using single call to affect thread state Java thread groups Hierarchical, groups can contain other groups Organized by name The default group is “main” Have independent max/min priorities, security permissions, stop/start/suspend/resume
Thread Groups Class ThreadGroup Parent and child thread groups Create and manipulate groups of threads Each group has unique name at creation Parent and child thread groups Method calls sent to parent group also sent to child groups
Groups Example aGroup.suspend(); bGroup.suspend(); class Groups { public static void main(String args[]) throws Exception { Thread.currentThread().setPriority (Thread.MAX_PRIORITY); ThreadGroup aGroup = new ThreadGroup("A"); aGroup.setMaxPriority(5); ThreadGroup bGroup = new ThreadGroup("B"); bGroup.setMaxPriority(5); for (int i=1; i <= 3; i++) { Thread p = new Thread(aGroup, new Proc(i)); p.start(); p = new Thread(bGroup, new Proc(i*10)); } aGroup.suspend(); bGroup.suspend(); System.out.println("Group A"); aGroup.resume(); Thread.sleep(90); System.out.println("Group A and B"); bGroup.resume(); Thread.sleep(90); System.out.println("None"); aGroup.stop(); bGroup.stop();
Groups Example class Proc implements Runnable { private int id; public Proc(int x) {id = x;} public void run() { try { while (true) { System.out.println("Hello from " + id); Thread.yield(); } catch (Exception e) {e.printStackTrace();}
Example Output Group A Hello from 1 Hello from 2 Hello from 3 Group A and B Hello from 10 Hello from 20 Hello from 30 None
Concurrency Limitations Safety Threads must synchronize access to resources Liveness Activities may fail to execute because of deadlock, too much synchronization, or not enough CPU cycles (starvation) Nondeterminism Concurrent program have many (possibly infinite) executions Thread vs. method call efficiency Not all messages are asynchronous Constructing a thread to handle a message is much slower than invoking an object’s method
Liveness Prevention Contention Dormancy Deadlock Other processes and threads utilizing CPU Dormancy Thread fails to become runnable (wait() with no notify()) Deadlock Waiting for a resource which will never become available All threads should allocate resource in same order
JVM Thread Implementations Native vs. non-native (e.g., “green” threads) Native: thread resources managed (scheduled) by underlying OS Non-native: threads managed by JVM, JVM is simply an OS process Native advantages Leverage multiprocessor hardware Can use thread libraries written in other languages VM can avoid inefficient I/O calls required by green threads
Scheduling Issues Ensure all threads (tasks) have sufficient CPU cycles Periodic/Aperiodic Tasks Periodic tasks execute on a regular schedule Take a temperature reading from a sensor every 100 ms Aperiodic tasks execute as response to an event User selects “spell check” Task types Execution: at precise date/time or after specified delay Periodic: execute every …
Java’s Timer Classes Timers schedule tasks (threads) for future background execution Java timer-related classes Timer Schedules tasks for execution TimerTask Base Class for all tasks scheduled by a Timer
Timer Example import java.util.*; class TimerExample { public static void main(String args[]) throws Exception { Timer timer = new Timer(); timer.schedule(new MyTask(1), 4000); timer.schedule(new MyTask(2), 2000); for (int i=0; i < 5; i++) { System.out.println("tick"); Thread.sleep(1000); } //Periodic tasks timer.scheduleAtFixedRate(new MyTask(3), 0, 500); timer.scheduleAtFixedRate(new MyTask(4), 0, 200); timer.cancel(); class MyTask extends TimerTask { int id; public MyTask(int myId) {id = myId;} public void run() {System.out.println("Executed #" + id);}
Timer Example Output tick Executed #2 // executed once after 2 seconds
Thread Synchronization (Revisited) Java uses monitors for thread synchronization The sychronized keyword Every synchronized method of an object has a monitor One thread inside a synchronized method at a time All other threads block until method finishes Next highest priority thread runs when method finishes
Producer/Consumer Relationship without Synchronization Buffer Shared memory region Producer thread Calls produce method to add item to buffer Calls wait if consumer has not read last message in buffer Writes to empty buffer and calls notify for consumer Consumer thread Reads message from buffer Calls wait if buffer empty Synchronize threads to avoid corrupted data
1 ProduceInteger.java 2 // Definition of threaded class ProduceInteger 3 public class ProduceInteger extends Thread { 4 private HoldIntegerUnsynchronized sharedObject; 5 6 // initialize ProduceInteger thread object 7 public ProduceInteger( HoldIntegerUnsynchronized shared ) 8 { 9 super( "ProduceInteger" ); 10 sharedObject = shared; 11 } 13 // ProduceInteger thread loops 10 times and calls 14 // sharedObject's setSharedInt method each time 15 public void run() 16 { 17 for ( int count = 1; count <= 10; count++ ) { 18 19 // sleep for a random interval 20 try { 21 Thread.sleep( ( int ) ( Math.random() * 3000 ) ); 22 } 23 24
33 // process InterruptedException during sleep 25 catch( InterruptedException exception ) { 26 System.err.println( exception.toString() ); 27 } 29 // call sharedObject method from this 30 // thread of execution 31 sharedObject.setSharedInt( count ); 32 } 34 System.err.println( 35 getName() + " finished producing values" + 36 "\nTerminating " + getName() ); 37 } 38 39 } // end class ProduceInteger
1 // ConsumeInteger.java 2 // Definition of threaded class ConsumeInteger 3 public class ConsumeInteger extends Thread { 4 private HoldIntegerUnsynchronized sharedObject; 5 6 // initialize ConsumerInteger thread object 7 public ConsumeInteger( HoldIntegerUnsynchronized shared ) 8 { 9 super( "ConsumeInteger" ); 10 sharedObject = shared; 11 } 12 13 // ConsumeInteger thread loops until it receives 10 14 // from sharedObject's getSharedInt method 15 public void run() 16 { 17 int value, sum = 0; 18 19 do { 20 21 // sleep for a random interval 22 try { 23 Thread.sleep( (int) ( Math.random() * 3000 ) ); 24 } 25 26
35 // process InterruptedException during sleep 27 catch( InterruptedException exception ) { 28 System.err.println( exception.toString() ); 29 } 30 31 value = sharedObject.getSharedInt(); 32 sum += value; 33 34 } while ( value != 10 ); 36 System.err.println( 37 getName() + " retrieved values totaling: " + sum + 38 "\nTerminating " + getName() ); 39 } 40 41 } // end class ConsumeInteger
1 // HoldIntegerUnsynchronized.java 2 // Definition of class HoldIntegerUnsynchronized. 3 public class HoldIntegerUnsynchronized { 4 private int sharedInt = -1; 5 6 // unsynchronized method to place value in sharedInt 7 public void setSharedInt( int value ) 8 { 9 System.err.println( Thread.currentThread().getName() + 10 " setting sharedInt to " + value ); 11 12 sharedInt = value; 13 } 14 15 // unsynchronized method return sharedInt's value 16 public int getSharedInt() 17 { 18 System.err.println( Thread.currentThread().getName() + 19 " retrieving sharedInt value " + sharedInt ); 20 21 return sharedInt; 22 } 23 24 } // end class HoldIntegerUnsynchronized
1 // SharedCell.java 2 // Show multiple threads modifying shared object. 3 public class SharedCell { 4 5 // execute application 6 public static void main( String args[] ) 7 { 8 HoldIntegerUnsynchronized sharedObject = 9 new HoldIntegerUnsynchronized(); 10 11 // create threads 12 ProduceInteger producer = 13 new ProduceInteger( sharedObject ); 14 ConsumeInteger consumer = 15 new ConsumeInteger( sharedObject ); 16 17 // start threads 18 producer.start(); 19 consumer.start(); 20 } 21 22 } // end class SharedCell
ConsumeInteger retrieving sharedInt value -1 ProduceInteger setting sharedInt to 1 ProduceInteger setting sharedInt to 2 ConsumeInteger retrieving sharedInt value 2 ProduceInteger setting sharedInt to 3 ProduceInteger setting sharedInt to 4 ProduceInteger setting sharedInt to 5 ConsumeInteger retrieving sharedInt value 5 ProduceInteger setting sharedInt to 6 ProduceInteger setting sharedInt to 7 ProduceInteger setting sharedInt to 8 ConsumeInteger retrieving sharedInt value 8 ProduceInteger setting sharedInt to 9 ConsumeInteger retrieving sharedInt value 9 ProduceInteger setting sharedInt to 10 ProduceInteger finished producing values Terminating ProduceInteger ConsumeInteger retrieving sharedInt value 10 ConsumeInteger retrieved values totaling: 49 Terminating ConsumeInteger
Producer/Consumer Relationship with Thread Synchronization Synchronize threads to ensure correct data
1 // ProduceInteger.java 2 // Definition of threaded class ProduceInteger 3 public class ProduceInteger extends Thread { 4 private HoldIntegerSynchronized sharedObject; 5 6 // initialize ProduceInteger thread object 7 public ProduceInteger( HoldIntegerSynchronized shared ) 8 { 9 super( "ProduceInteger" ); 10 sharedObject = shared; 11 } 12 13 // ProduceInteger thread loops 10 times and calls 14 // sharedObject's setSharedInt method each time 15 public void run() 16 { 17 for ( int count = 1; count <= 10; count++ ) { 18 19 // sleep for a random interval 20 try { 21 Thread.sleep( ( int ) ( Math.random() * 3000 ) ); 22 } 23 24
34 // process InterruptedException during sleep 25 catch( InterruptedException exception ) { 26 System.err.println( exception.toString() ); 27 } 28 29 // call sharedObject method from this 30 // thread of execution 31 sharedObject.setSharedInt( count ); 32 } 33 System.err.println( 35 getName() + " finished producing values" + 36 "\nTerminating " + getName() ); 37 } 38 39 } // end class ProduceInteger
1 // ConsumeInteger.java 2 // Definition of threaded class ConsumeInteger 3 public class ConsumeInteger extends Thread { 4 private HoldIntegerSynchronized sharedObject; 5 6 // initialize ConsumerInteger thread object 7 public ConsumeInteger( HoldIntegerSynchronized shared ) 8 { 9 super( "ConsumeInteger" ); 10 sharedObject = shared; 11 } 12 13 // ConsumeInteger thread loops until it receives 10 14 // from sharedObject's getSharedInt method 15 public void run() 16 { 17 int value, sum = 0; 18 19 do { 20 21 // sleep for a random interval 22 try { 23 Thread.sleep( (int) ( Math.random() * 3000 ) ); 24 } 25 26
35 // process InterruptedException during sleep 27 catch( InterruptedException exception ) { 28 System.err.println( exception.toString() ); 29 } 30 31 value = sharedObject.getSharedInt(); 32 sum += value; 33 34 } while ( value != 10 ); 36 System.err.println( 37 getName() + " retrieved values totaling: " + sum + 38 "\nTerminating " + getName() ); 39 } 40 41 } // end class ConsumeInteger
1 // HoldIntegerSynchronized.java 2 // Definition of class HoldIntegerSynchronized that 3 // uses thread synchronization to ensure that both 4 // threads access sharedInt at the proper times. 5 public class HoldIntegerSynchronized { 6 private int sharedInt = -1; 7 private boolean writeable = true; // condition variable 8 9 // synchronized method allows only one thread at a time to 10 // invoke this method to set the value for a particular 11 // HoldIntegerSynchronized object 12 public synchronized void setSharedInt( int value ) 13 { 14 while ( !writeable ) { // not the producer's turn 15 16 // thread that called this method must wait 17 try { 18 wait(); 19 } 20 21 // process Interrupted exception while thread waiting 22 catch ( InterruptedException exception ) { 23 exception.printStackTrace(); 24 } 25 } 26 27 System.err.println( Thread.currentThread().getName() + 28 " setting sharedInt to " + value ); 29 30 // set new sharedInt value 31 sharedInt = value; 32
33 // indicate that producer cannot store another value until 34 // a consumer retrieve current sharedInt value 35 writeable = false; 36 37 // tell a waiting thread to become ready 38 notify(); 39 } 40 41 // synchronized method allows only one thread at a time to 42 // invoke this method to get the value for a particular 43 // HoldIntegerSynchronized object 44 public synchronized int getSharedInt() 45 { 46 while ( writeable ) { // not the consumer's turn 47 48 // thread that called this method must wait 49 try { 50 wait(); 51 } 52 53 // process Interrupted exception while thread waiting 54 catch ( InterruptedException exception ) { 55 exception.printStackTrace(); 56 } 57 } 58 59
68 // indicate that producer cant store another value 60 // because a consumer just retrieved sharedInt value 61 writeable = true; 62 63 // tell a waiting thread to become ready 64 notify(); 65 66 System.err.println( Thread.currentThread().getName() + 67 " retrieving sharedInt value " + sharedInt ); 69 return sharedInt; 70 } 71 72 } // end class HoldIntegerSynchronized
1 // SharedCell.java 2 // Show multiple threads modifying shared object. 3 public class SharedCell { 4 5 // execute application 6 public static void main( String args[] ) 7 { 8 HoldIntegerSynchronized sharedObject = new HoldIntegerSynchronized(); 10 11 // create threads 12 ProduceInteger producer = new ProduceInteger( sharedObject ); 14 ConsumeInteger consumer = new ConsumeInteger( sharedObject ); 16 17 // start threads 18 producer.start(); 19 consumer.start(); 20 } 21 22 } // end class SharedCell
ProduceInteger setting sharedInt to 1 ConsumeInteger retrieving sharedInt value 1 ProduceInteger setting sharedInt to 2 ConsumeInteger retrieving sharedInt value 2 ProduceInteger setting sharedInt to 3 ConsumeInteger retrieving sharedInt value 3 ProduceInteger setting sharedInt to 4 ConsumeInteger retrieving sharedInt value 4 ProduceInteger setting sharedInt to 5 ConsumeInteger retrieving sharedInt value 5 ProduceInteger setting sharedInt to 6 ConsumeInteger retrieving sharedInt value 6 ProduceInteger setting sharedInt to 7 ConsumeInteger retrieving sharedInt value 7 ProduceInteger setting sharedInt to 8 ConsumeInteger retrieving sharedInt value 8 ProduceInteger setting sharedInt to 9 ConsumeInteger retrieving sharedInt value 9 ProduceInteger setting sharedInt to 10 ProduceInteger finished producing values Terminating ProduceInteger ConsumeInteger retrieving sharedInt value 10 ConsumeInteger retrieved values totaling: 55 Terminating ConsumeInteger