CS 596 Java Programming Fall Semester, 1998 Threads part 3 |
||
---|---|---|
© 1998, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 04-Jan-99 |
Safety - Mutual Access
With multiprocessing we need to address mutual access by different threads. When two or more threads simultaneously access the same data there may be problems.
Some types of access are safe. If a method accesses just local data, then multiple threads can safely call the method on the same object. Assignment statements of all types, except long and double, are atomic. That is a thread can not be interrupted by another thread while performing an atomic operation.
class AccessExample { int[] data; int safeInt; public String toString() { return "array length " + data.length + " array values " + data[0]; } public void safeCode( int size, int startValue){ int[] verySafe = new int[ size ]; for ( int index = 0; index < size; index++ ) verySafe[ index ] = (int ) Math.sin( index * startValue ); } public void setInt( int aValue ) { safeInt = aValue; } public void dangerousCode( int size, int startValue) { data = new int[ size ]; for ( int index = 0; index < size; index++ ) data[ index ] = (int ) Math.sin( index * startValue ); } }
Mutual Access Problem class Trouble extends Thread { int size; int startValue; AccessExample data; public Trouble( int aSize, int aStartValue, AccessExample myData ) { size = aSize; startValue = aStartValue; data = myData; } public void run() { for (int k = 0; k < 10; k++ ) { data.setInt( size); data.safeCode( size, startValue ); data.dangerousCode( size, startValue); } } } public class Test { public static void main( String args[] ) throws Exception { AccessExample someData = new AccessExample(); Trouble one = new Trouble( 500000, 0, someData ); Trouble two = new Trouble( 3, 22, someData ); one.start(); two.start(); two.join(); one.join(); System.out.println( someData ); } }Output rohan 31-> j2 -native Test java.lang.ArrayIndexOutOfBoundsException: 3 at AccessExample.dangerousCode(Compiled Code) at Trouble.run(Compiled Code) array length 3 array values 0
Synchronize
Synchronize is Java's mechanism to insure that only one thread at a time will access a piece of code. We can synchronize methods and block's of code (synchronize statements).
Synchronized Instance Methods
When a thread executes a synchronized instance method on an object, that object is locked. The object is locked until the method ends. No other thread can execute any synchronized instance method on that object until the lock is released. The thread that has the lock can execute multiple synchronized methods on the same object. The synchronization is on a per object bases. If you have two objects, then different threads can simultaneously execute synchronized methods on different objects. Unsynchronized methods can be executed on a locked object by any thread at any time. The JVM insures that only one thread can obtain a lock on an object at a time.
class SynchronizeExample { int[] data; public String toString() { return "array length " + data.length + " array values " + data[0]; } public synchronized void initialize( int size, int startValue){ data = new int[ size ]; for ( int index = 0; index < size; index++ ) data[ index ] = (int ) Math.sin( index * startValue ); } public void unSafeSetValue( int newValue) { for ( int index = 0; index < data.length; index++ ) data[ index ] = (int ) Math.sin( index * newValue ); } public synchronized void safeSetValue( int newValue) { for ( int index = 0; index < data.length; index++ ) data[ index ] = (int ) Math.sin( index * newValue ); } }
Synchronized Static Methods
A synchronized static method creates a lock on the class, not the object. When one thread has a lock on the class, no other thread can execute any synchronized static method of that class. Other threads can execute synchronized instance methods on objects of that class.
class SynchronizeStaticExample { int[] data; static int[] classData public synchronized void initialize( int size, int startValue){ data = new int[ size ]; for ( int index = 0; index < size; index++ ) data[ index ] = (int ) Math.sin( index * startValue ); } public synchronized void initializeStatic( int size, int startValue){ classData = new int[ size ]; for ( int index = 0; index < size; index++ ) classData[ index ] = (int ) Math.sin( index * startValue ); } }
Synchronized Statements
A block of code can be synchronized. The basic syntax is:
synchronized ( expr ) { statements }The expr must evaluate to an object. This will lock the object. The lock is released when the thread finishes the block. Until the lock is released, no other thread can enter any method or synchronized block that is locked by the given object.
A synchronized method is syntactic sugar for a synchronized block.
class LockTest { public synchronized void enter() { System.out.println( "In enter"); } }Is the same as:
class LockTest { public void enter() { synchronized ( this ) { System.out.println( "In enter"); } } }
Lock for Block and Method This example shows that a lock on an object also locks all access to the object via synchronized methods.
public class Test { public static void main( String args[] ) throws Exception { LockTest aLock = new LockTest(); TryLock tester = new TryLock( aLock ); tester.start(); synchronized ( aLock ) { System.out.println( "In Block"); Thread.currentThread().sleep( 5000); System.out.println( "End Block"); } } } class TryLock extends Thread { private LockTest myLock; public TryLock( LockTest aLock ) { myLock = aLock; } public void run() { System.out.println( "Start run"); myLock.enter(); System.out.println( "End run"); } } class LockTest { public synchronized void enter() { System.out.println( "In enter"); } }Output In Block Start run End Block In enter End run
Deadlock The following code creates a deadlock
class Friendly extends Thread { private Friendly aFriend; public Friendly( String name ) { super( name ); } public void setFriend( Friendly myFriend ) { aFriend = myFriend;} public synchronized void hug() { try { System.out.println( "I " + getName() + " am hugged "); sleep( 5 ); aFriend.rehug(); } catch ( InterruptedException notInThisExample ){} } public synchronized void rehug() { System.out.println( "I " + getName() + " am rehugged "); } public void run() {aFriend.hug(); } } public class Test { public static void main( String args[] ) { Friendly fred = new Friendly("Fred"); Friendly sam = new Friendly( "Sam"); fred.setFriend( sam ); sam.setFriend( fred ); fred.start(); sam.start(); System.out.println( "End" ); } }Output End I Fred am hugged I Sam am hugged
Deadlock Avoided Here we show how to avoid the deadlock of the previous slide.
class Friendly extends Thread { private Friendly aFriend; private Object lock; public Friendly( String name, Object lock ) { super( name ); this.lock = lock; } public void setFriend( Friendly myFriend ) { aFriend = myFriend; } public synchronized void hug() { try { System.out.println( "I " + getName() + " am hugged "); sleep( 5 ); aFriend.rehug(); } catch ( InterruptedException notInThisExample ) {} } public synchronized void rehug() { System.out.println( "I " + getName() + " am rehugged "); } public void run() { synchronized ( lock) { aFriend.hug(); } } }
//Deadlock Avoided Continued public class Test { public static void main( String args[] ) //throws Exception { Object aLock = "Schalage"; Friendly fred = new Friendly("Fred", aLock); Friendly sam = new Friendly( "Sam", aLock); fred.setFriend( sam ); sam.setFriend( fred ); fred.start(); sam.start(); System.out.println( "End" ); } }Output End I Sam am hugged I Fred am rehugged I Fred am hugged I Sam am rehugged
Synchronized and Inheritance
If you want a method in a subclass to be synchronized you must declare it to be synchronized.
class Top { public void synchronized left() { // do stuff } public void synchronized right() { // do stuff } } class Bottom extends Top { public void left() { // not synchronized } public void right() { // do stuff not synchronized super.right(); // synchronized here // do stuff not synchronized }
Volatile
Java allows threads that access shared variables to keep private working copies of the variables. This improves the performance of multiple threaded programs. These working copies are reconciled with the master copies in shared main memory when objects are locked or unlocked. If you do not wish to use synchronized, Java has a second method to make sure that threads are using the proper value of shared variables. If a field is declared volatile, then a thread must reconcile its working copy of the field every time it accesses the variable. Operations on the master copy of the variable are performed in exactly the order that the thread requested. In the example on the next slide, a threads copy of the field "value" can get out of synch with its actual value.
Volatile Example class ExampleFromTheBook { int value; volatile int volatileValue; public void setValue( int newValue ) { value = newValue; volatileValue = newValue; } public void display() { value = 5; volatileValue = 5; for ( int k = 0; k < 5; k++ ) { System.out.println( "Value " + value ); System.out.println("Volatile " + volatileValue ); Thread.yield( ); } } } class ChangeValue extends Thread { ExampleFromTheBook myData; public ChangeValue( ExampleFromTheBook data ) { myData = data; } public void run() { for ( int k = 0; k < 5; k++ ) { myData.value = k; myData.volatileValue = k; Thread.yield( ); } } }
public class Test { public static void main( String args[] ) { ExampleFromTheBook example = new ExampleFromTheBook(); ChangeValue changer = new ChangeValue( example ); changer.start(); example.display(); } }Some of the Output Value 5 Volatile 1 Value 2 Volatile 2 Value 3 Volatile 3
wait and notify Methods in Object
wait and notify are some of the most useful thread operations.
public final void wait(timeout) throws InterruptedException
synchronized void waitingMethod() { while ( ! condition ) wait(); Now do what you need to do when condition is true }
synchronized void changeMethod() { Change some value used in a condition test notify(); }
wait and notify Example Over the next five slides is a typical consumer-producer example. Producers "make" items, which they put into a queue. Consumers remove items from the queue. What happens when the consumer wishes to remove when the queue is empty? Using threads, we can have the consumer thread wait until a producer thread adds items to the queue.
class Queue { LinkedCell head; LinkedCell tail; public synchronized void append( Object item ) { LinkedCell newItem = new LinkedCell( item ); if ( head == null ) head = newItem; else tail.append( newItem ); tail = newItem; notify(); } public synchronized Object get( ) { try { while ( head == null ) wait(); } catch (InterruptedException threadIsDone ) { return null; } Object returnMe = head.element(); head = head.next(); return returnMe; } }
wait and notify - Helper
class LinkedCell { Object cellItem; LinkedCell next; public LinkedCell( Object value ) { cellItem= value; } public Object element() { return cellItem; } public void append( LinkedCell nextCell ) { next = nextCell; } public LinkedCell next() { return next; } }
wait and notify - Producer class Producer extends Thread { Queue factory; int workSpeed; public Producer( String name, Queue output, int speed ) { setName(name); factory = output; workSpeed = speed; } public void run() { try { int product = 0; while (true) // work forever { System.out.println( getName() + " produced " + product); factory.append( getName() + String.valueOf( product) ); product++; sleep( workSpeed); } } catch ( InterruptedException WorkedToDeath ) { return; } } }
wait and notify - Consumer class Consumer extends Thread { Queue localMall; int sleepDuration; public Consumer( String name, Queue input, int speed ) { setName(name); localMall = input; sleepDuration = speed; } public void run() { try { while (true) // Shop until you drop { System.out.println( getName() + " got " + localMall.get()); sleep( sleepDuration ); } } catch ( InterruptedException endOfCreditCard ) { return; } } }
wait and notify - Driver Program class Test { public static void main( String args[] ) throws Exception { Queue wallmart = new Queue(); Producer nike = new Producer( "Nike", wallmart, 500 ); Producer honda = new Producer( "Honda", wallmart, 1200 ); Consumer valleyGirl = new Consumer( "Sue", wallmart, 400); Consumer valleyBoy = new Consumer( "Bob", wallmart, 900); Consumer dink = new Consumer( "Sam", wallmart, 2200); nike.start(); honda.start(); valleyGirl.start(); valleyBoy.start(); dink.start(); } }Output
Nike produced 0 | Sue got Nike3 | Honda produced 3 |
Honda produced 0 | Nike produced 4 | Bob got Honda3 |
Sue got Nike0 | Sue got Nike4 | Nike produced 8 |
Bob got Honda0 | Honda produced | Sue got Nike8 |
Nike produced 1 | Bob got Honda2 | Nike produced 9 |
Sam got Nike1 | Nike produced 5 | Sue got Nike9 |
Nike produced 2 | Sue got Nike5 | Honda produced 4 |
Sue got Nike2 | Nike produced 6 | Bob got Honda4 |
Honda produced 1 | Sam got Nike6 | Nike produced 10 |
Bob got Honda1 | Nike produced 7 | Sue got Nike10 |
Nike produced 3 | Sue got Nike7 | Nike produced 11 |
Piped Streams & Threads
In most streams, one end of the stream is "connected" to a file, socket, keyboard, etc. With piped streams, both ends are in your program. This allows one thread to write data via a stream to another thread in your program. The following example illustrates this.
class TestIO { public static void main( String[] args ) throws IOException { PipedReader inPipe = new PipedReader(); PipedWriter outPipe = new PipedWriter(inPipe); PrintWriter cout = new PrintWriter( outPipe ); ReadThread reader = new ReadThread( "Read", inPipe ); reader.setPriority( 6 ); // 5 is normal priority reader.start(); System.out.println( "In Main" ); cout.println( "Hello" ); System.out.println( "End" ); } }
Messages between Threads import java.io.*; import sdsu.io.UnicodeReader;
class ReadThread extends Thread { private UnicodeReader cin; public ReadThread( String name, PipedReader input ) { super( name ); cin = new UnicodeReader( input ); } public void run() { try { System.out.println( "Start " + getName() ); String message = cin.readWord(); System.out.println( message + " From: " + getName() ); } catch ( Exception ignored ) {} } }Output Start Read In Main End Hello From: Read
ThreadLocal Variables
JDK 1.2 introduces ThreadLocal variables. Here's what the on-line docs say about them:
"These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal objects are typically private static variables in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a ThreadLocal as long as the thread is alive and the ThreadLocal object is accessible; after a thread goes away, all of its copies of ThreadLocal variables are subject to garbage collection (unless other references to these copies exist)."
The following two slides give examples of the use of ThreadLocal variables. The key to understanding the examples is:
1) Each thread has its own copy of the ThreadLocal value
2) The initial value for each thread is null
3) In deciding which thread to assign the value to, it does not matter what code sets the value of the ThreadLocal, what matters in what thread was running.
ThreadLocal Example Correct Way class LocalExample extends Thread { private static ThreadLocal name = new ThreadLocal(); LocalExample myFriend; public LocalExample( String myName ) {super( myName ); } public void run() { name.set( getName() ); System.out.println( "My name " + name() ); System.out.println( "My friend's name " + myFriend.name() ); } public String name() { return (String) name.get(); } public void setFriend( LocalExample anExample ) { myFriend = anExample; } } public class Test { public static void main(String args[]) throws InterruptedException{ LocalExample a = new LocalExample( "A" ); LocalExample b = new LocalExample( "B" ); a.setFriend( b ); b.setFriend( a ); a.start(); b.start(); a.join(); b.join(); System.out.println( "A's name " + a.name() ); } }Output My name B My friend's name B My name A My friend's name A A's name null
ThreadLocal Example Wrong Way class LocalExample extends Thread { private static ThreadLocal name = new ThreadLocal(); LocalExample myFriend; public LocalExample( String myName ) { name.set( myName );} public void run() { System.out.println( "My name " + name() ); System.out.println( "My friend's name " + myFriend.name() ); } public String name() { return (String) name.get(); } public void setFriend( LocalExample anExample ) { myFriend = anExample; } } public class Test { public static void main(String args[]) throws InterruptedException{ LocalExample a = new LocalExample( "A" ); LocalExample b = new LocalExample( "B" ); a.setFriend( b ); b.setFriend( a ); a.start(); b.start(); a.join(); b.join(); System.out.println( "A's name " + a.name() ); } }Output My name null My friend's name null My name null My friend's name null A's name B
InheritableThreadLocal Variables
These act like process variables in Unix. Child threads automatically get a copy of the parent's threads values.
From the on-line documentation:
"This class extends ThreadLocal to provide inheritance of values from parent Thread to child Thread: when a child thread is created, the child receives initial values for all InheritableThreadLocals for which the parent has values. Normally the child's values will be identical to the parent's; however, the child's value can be made an arbitrary function of the parent's by overriding the childValue method in this class."
InheritableThreadLocal Variables Example class LocalExample extends Thread { private static InheritableThreadLocal name = new InheritableThreadLocal(); private static InheritableThreadLocal id = new InheritableThreadLocal(); private static ThreadLocal color = new ThreadLocal(); static int count = 0; public void run() { if ( count == 0 ) { count++; name.set( "A" ); id.set( new Integer( count )); color.set( "Green" ); System.out.println( "Parent " + name.get() + " " + id.get() + " " + color.get() ); LocalExample child = new LocalExample(); child.start(); } else if ( count > 0 ) { System.out.println( "Child " + name.get() + " " + id.get() + " " + color.get() ); } } } public class Test { public static void main(String args[]) { LocalExample a = new LocalExample( ); a.start(); } }Output Parent A 1 Green
Child A 1 null
Debugging Threads
Some useful methods in Thread for debugging
public static void dumpStack()