Given by Lukasz Beca at Tango Group Internal Technology Seminars on Spring 99. Foils prepared May 19 99
Outside Index
Summary of Material
Motivation |
Support for threads in JAVA |
Safety |
Liveness |
State-dependent actions |
Design issues |
Final remarks |
Outside Index Summary of Material
Lukasz Beca |
NPAC, Syracuse University |
Motivation |
Support for threads in JAVA |
Safety |
Liveness |
State-dependent actions |
Design issues |
Final remarks |
Several tasks at the same time |
Availability |
Asynchronous messages |
Parallelism |
Implementation requirements |
Increased complexity |
Safety problems |
Liveness problems |
Nondeterminism |
Overhead
|
Class java.lang.Thread used to initiate and control new activities |
Keywords
|
control execution of code in objects that may participate in multiple threads |
Methods in java.lang.Object - coordinate activities across threads
|
Synchronized method |
Synchronized block |
Unsynchronized method |
Lock |
Monitor Queue |
Object |
public synchronized void methodA(){ |
// any code |
} |
objectA.methodA(); |
synchronized(objectA){ |
// any code |
} |
Thread class
|
Runnable interface, used if the class must subclass some other class (the most common example being Applet) |
public class Printer implements Runnable { |
protected String _text; |
public Printer(String text) { |
_text = text; |
} |
public void run() { |
System.out.println(_text); |
} |
} |
// code here |
Printer hello = new Printer("hello"); |
Thread helloThread = new Thread(hello); |
helloThread.start(); |
// code here |
main thread |
main thread continues |
new thread started |
start/run |
public class SimpleThread extends Thread { |
public SimpleThread(String str) { |
super(str); |
} |
public void run() { |
System.out.println("DONE! " + getName()); |
} |
} |
public class TwoThreadsTest { |
public static void main (String[] args) { |
new SimpleThread("Jamaica").start(); |
new SimpleThread("Fiji").start(); |
} |
} |
start calls run method as an independent activity |
stop irrevocably terminates a thread |
isAlive returns true if thread started but not terminated |
suspend temporarily halts a thread |
resume makes thread to continue if it was suspended |
sleep suspends for a given time |
join suspends caller till the target thread completes |
interrupt causes a sleep, wait, or join to abort with InterruptedException |
Priority based scheduling queue |
Default: thread has the same priority as the thread that created it |
setPriority method changes a priority |
yield method relinquishes control |
Algorithm:
|
Primitive operations are atomic
|
synchronized methods |
synchronized(anyObject){ anyCode()} blocks |
class-level static methods and blocks synchronization |
synchronized qualifier can be overridden in subclasses |
Methods wait, notify and notifyAll can be invoked only when the synchronization lock is held on their targets |
wait - resulting actions
|
notify - resulting actions
|
notifyAll works in the same way, but the steps occur for all threads in the wait queue |
wait with time parameter - notify is invoked automatically when the specified time passes |
Nothing bad ever happens |
Causes of safety problems
|
Safe objects
|
All methods accessing object state are synchronized |
Static fields and methods: use Class locks |
Partial synchronization: achieved using synchronized(this) code blocks within critical sections |
public class LinkedCell { |
protected double value_; // mutable |
protected LinkedCell next_; // immutable |
public LinkedCell(double v, LinkedCell t) { |
value_ = v; |
next_ = t; |
} |
public synchronized double value() { |
return value_; |
} |
public synchronized void setValue(double v) { |
value_ = v; |
} |
public LinkedCell next() { |
return next_; |
} |
public boolean includes(double x) { |
synchronized(this) { |
if (value_ == x) |
return true; |
} |
if (next() == null) |
return false; |
else |
return next().includes(x); |
} |
Inner objects are constructed during construction of outer object |
Outer object never passes references to inner objects accessible to any other object |
Outer object is fully synchronized or is in turn uniquely embedded within another fully synchronized object |
Something finally happens |
Liveness failures
|
Instance variable analysis: allows to find methods that need not be synchronized |
Splitting synchronization: partition of class into independent, non-interacting subsets |
Unsynchronized accessors
|
Unsynchronized updates
|
Splitting classes: more efficient use of fine-grained synchronization measures, component classes are independently synchronized |
Splitting locks: separate synchronization of different subsets of functionality |
public class Shape { |
protected double x_ = 0.0; |
protected double y_ = 0.0; |
protected double width_ = 0.0; |
protected double height_ = 0.0; |
public synchronized double x() { |
return x_; |
} |
public synchronized double width() { |
return width_; |
} |
// ... |
} |
public class BetterShape { |
protected Location loc_ = new Location(0,0); |
protected Dimentions dim_ = new Dimentions(0,0); |
public double x() { |
return loc_.getX(); |
} |
public double width() { |
return dim_.getWidth(); |
} |
// ... |
} |
public class EvenBetterShape { |
protected double x_ = 0.0; |
protected double width_ = 0.0; |
protected Object locationLock_ = new Object(); |
protected Object dimensionLock_ = new Object(); |
public double x() { |
synchronized(locationLock_) { |
return x_; |
} |
} |
public double width() { |
synchronized(dimensionLock_) { |
return width_; |
} |
} |
} |
Action triggering conditions:
|
State-based preconditions: object must be in proper state upon the reception of the message |
State-based postconditions: object must be in proper state when the message handling is finished |
Inaction ignore the request |
Balking return failure indication to client |
Guarded suspension (guarding) suspend execution until preconditions become true |
Provisional action pretend to perform an action, but do not commit until success is assured |
Rollback/Recovery try to proceed but do the rollback upon failure |
Retry repeatedly attempt action |
For each condition that needs to be waited on, write a guarded wait loop |
Ensure that every method causing state changes that affects the conditions invokes notifyAll to wake up any threads waiting for state changes |
public class GuardedClass { |
protected boolean cond_ = false; |
protected synchronized void awaitCond() { |
while(!cond) { |
try { wait(); } |
catch(InterrrupedException ex) {} |
} |
} |
public synchronized void guardedAction() { |
awaitCond(); |
//actions |
} |
public synchronized void setCond(boolen cond) { |
cond_ = cond; |
notifyAll(); |
} |
} |
Define appropriate exception types to be thrown to indicate the failure |
Check preconditions and conditionally return failure before taking any irreversible action in the method |
Make public accessor methods so that clients can avoid or minimize failures |
public class IncrementException extends Exception {} |
public class BalkingBoundedCounter { |
protected long count_ = BoundedCounter.MIN; |
public synchronized void inc() throws IncrementException { |
if (count_ >= BoundedCounter.MAX) |
throw new IncrementException(); |
else |
++ count_; |
} |
} |
Means of detecting failure
|
Policy for dealing with failure
|
Means of dealing with the consequences of actions leading to failure
|
public class Optimistic { |
private State currentState_; |
protected synchronized boolean commit(State assumed, State next) { |
boolean success = (currentState_ = assumed); |
if (success) |
currentState_ = next; |
return success; |
} |
} |
Concurrency control - layering of synchronization and control policies over base mechanisms |
Services in threads - how and when to create and invoke thread-based services |
Flow patterns - design of system composed of connected stages with assigned producer and consumer roles |
Transactions - operations performed without interference from other threads |
Notifications - information about changes in state and events |
Scheduling - implementing proprietary scheduling schemes |
Design thread structure and interactions carefully |
Do not use threads if it is not necessary |
Never make assumptions about relative rate of progress among threads |
Assume that execution of any thread can be preempted at any point |
Concurrent Programming in Java: Design Principles and Patterns by Doug Lea |
Java Tutorial: http://www.java.sun.com/docs/books/tutorial/trailmap.html |
Java API documentation |