java threads
A thread is an independent flow of execution
in a program. A program starts with one thread,
but can create many more. Each thread is
independent of all other threads.
Why threads? Threads
-
allow user interaction while waiting for
an external request to be satisfied.
-
allow multiple concurrent requests,
this reduces latency.
-
allow servers to handle multiple requests.
-
can support parallel processing on multi-processor
machines.
-
can create games with concurrent activity.
-
are required for multimedia.
creating threads
Threads are created by extending the
java.lang.Thread
class or by passing a class that implements the
java.lang.Runnable to a
java.lang.Thread instance.
Creating threads by extending the java.lang.Thread is
shown in the Babble class.
/**
* Babble is an example of thread creation,
* each Babble thread prints a messages, waits
* a fixed time, and repeats forever.
*/
class Babble extends Thread {
private String msg;
private int delay;
public Babble( String msg, int delay ) {
this.msg = msg;
this.delay = delay;
}
public void run() {
try {
for( ;; ) { // common infinte loop idiom
System.out.println( msg );
sleep( delay );
}
}
catch( InterruptedException e ) {
return; // end of this thread
}
}
/**
* Creates two babblng threads, the initial
* thread exists.
*/
public static void main( String[] args ) {
Thread t1 = new Babble( "hi", 250 );
Thread t2 = new Babble( "hello", 500 );
t1.start();
t2.start();
}
}
runnable interface
The Thread class and Runnable interface specify the
void run()
method. Execution of the new thread starts
with the call to the run method. The thread terminates
when the run method returns.
/**
* Babble is an example of thread creation,
* each Babble thread prints a messages waits
* a fixed time, and repeats forever.
*/
class RunnableBabble implements Runnable {
private String msg;
private int delay;
public RunnableBabble( String msg, int delay ) {
this.msg = msg;
this.delay = delay;
}
public void run() {
try {
for( ;; ) { // common infinte loop idiom
System.out.println( msg );
Thread.sleep( delay );
}
}
catch( InterruptedException e ) {
return; // end of this thread
}
}
/**
* Creates two babble threads, the initial
* thread exists.
*/
public static void main( String[] args ) {
Thread t1 = new Thread( new RunnableBabble( "hi", 200 ));
Thread t2 = new Thread( new RunnableBabble( "hello", 600 ));
t1.start();
t2.start();
}
}
threading in java
How does one CPU execute more than one thread?
Multitasking is supported by the Operating
System and hardware with time sharing.
In time sharing, a process/thread is allowed to
executed for a fixed period before being
preempted and suspended. The OS then picks
the next thread/process to resume. The time
sharing of N processes/threads is equivalent to having
N CPUs with 1/Nth of the speed.
A thread can be runnable or blocked. A runnable
thread can continue execution when resumed.
A blocked threaded is waiting for an external
event (outside of the thread) and
is not runnable until the external event occurs.
Threads are normally blocked when performing
I/O or when waiting for an external signal.
Threads share a common memory space.
Processes are placed in their own memory space.
Threads are called light-weight processes,
since the overhead of switching between threads
is much cheaper than switching processes.
Each thread has its own stack to store local variables
and activation records.
count down
The CountDown program has two threads,
one that will terminate the program
after a fixed count if not stopped.
The other thread accepts user input and if
given "stop" will prevent the count down
from exiting the program.
import java.io.*;
import java.io.InputStreamReader;
/**
* Count down to termination unless stopped.
*/
public class CountDown implements Runnable {
private int count;
private boolean stop;
public CountDown( int count ) {
this.count = count;
this.stop = true;
}
public void stop() {
stop = false;
}
public void run() {
try {
for( int i = 0; i < count && stop ; i++ ) {
System.out.println( i );
Thread.sleep( 1000 );
}
if ( stop ) {
System.out.println("too late");
System.exit( 0 );
}
else {
System.out.println("stop aborted");
}
}
catch( InterruptedException ex ) {
}
}
/**
* The initial thread starts with main.
*/
public static void main( String[] args ) throws IOException {
if ( args.length != 1 ) {
System.out.println("java CountDown limit");
System.exit( 1 );
}
int limit = Integer.parseInt( args[0] );
BufferedReader in
= new BufferedReader(
new InputStreamReader(System.in));
CountDown cd = new CountDown( limit );
Thread t = new Thread( cd );
// start the second thread.
t.start();
String line = null;
while ( (line=in.readLine()) != null ){
if ( line.equals("stop") ) {
cd.stop();
System.out.println("stop requested");
break;
}
else if ( line.equals("quit") ) {
System.out.println("quit main");
break;
}
else {
System.out.println("what?");
}
}
}
}
count down with interrupt
The following code will work on some JVM.
In general, interrupting a thread will only cause
an immediate interrupt when the thread is blocked
on a wait or sleeping.
import java.io.*;
import java.io.InputStreamReader;
/**
* Count down to termination unless stopped.
*/
public class CountDown1 implements Runnable {
private int count;
private boolean stop;
private Thread th;
public CountDown1( int count, Thread th ) {
this.count = count;
this.stop = true;
this.th = th;
}
public void stop() {
stop = false;
}
public void run() {
try {
for( int i = 0; i < count && stop ; i++ ) {
System.out.println( i );
Thread.sleep( 1000 );
}
if ( stop ) {
System.out.println("too late");
//System.exit( 0 );
th.interrupt();
}
else {
System.out.println("stop aborted");
}
}
catch( InterruptedException ex ) {
}
}
/**
* The initial thread starts with main.
*/
public static void main( String[] args ) throws IOException {
if ( args.length != 1 ) {
System.out.println("java CountDown1 limit");
System.exit( 1 );
}
int limit = Integer.parseInt( args[0] );
BufferedReader in
= new BufferedReader(
new InputStreamReader(System.in));
Thread cur = Thread.currentThread();
CountDown1 cd = new CountDown1( limit, cur );
Thread t = new Thread( cd );
// start the second thread.
t.start();
String line = null;
while ( true ) {
try {
line = in.readLine();
}
catch( java.io.InterruptedIOException ex ) {
System.out.println("io interrupted, exiting");
break;
}
if ( line.equals("stop") ) {
cd.stop();
System.out.println("stop requested");
break;
}
else {
System.out.println("what?");
}
if (cur.isInterrupted() ) {
System.out.println("isInterrupted() is true");
}
}
}
}
racing
Bad things can happen when too much is
happening. What does the following program
output:
/**
* Concurrency can cause confusion
*/
class Race extends Thread {
private int count;
private Accumulate acc;
public Race( int count, Accumulate acc ) {
this.count = count;
this.acc = acc;
}
public void run() {
for( int i = 0; i < count ; i++ ) {
acc.inc();
}
}
public static void main( String[] args ) {
if ( args.length != 1 ) {
System.out.println("java Race loops");
System.exit( 1 );
}
int loops = Integer.parseInt( args[0] );
long t = System.currentTimeMillis();
Accumulate acc = new Accumulate( loops );
Thread t1 = new Race( 9000, acc );
Thread t2 = new Race( 8000, acc );
t1.start();
t2.start();
try {
t1.join(); // wait for t1 to die
t2.join(); // wait for t2 to die
}
catch( InterruptedException ex ) {
}
System.out.println( acc.getCount() + " == " + 17000 );
t = System.currentTimeMillis() - t;
System.out.println( "elapsed = " + t );
}
}
class Accumulate {
private int sum;
private int loops;
public Accumulate( int loops ) {
this.loops = loops;
this.sum = 0;
}
public void inc() {
int s = sum;
for( int i = 0 ; i < loops ; i++ )
; // do nothing
sum = s + 1;
}
public int getCount() {
return sum;
}
}
Thread t1 and t2 each call acc.inc(),
these calls occur concurrently.
On a single processor their execution is interleaved.
Thread t1 calls acc.inc(), sometimes this thread is suspended
in the middle of the for loop in acc. The assignment,
int s = sum;
which occurs before the for loop, saves the value of sum in s.
Thread t2 runs and calls t1.acc() several times before
being suspended. When t1 resumes it updates sum with
value saved in s, by
sum = s + 1;
thus any increments caused by t2 will be lost.
Verifying overlap
The CountRace class counts the number of overlaps.
/**
* Count the overlaps
*/
class CountRace extends Thread {
private int count;
private CountAccumulate acc;
public CountRace( int count, CountAccumulate acc ) {
this.count = count;
this.acc = acc;
}
public void run() {
for( int i = 0; i < count ; i++ ) {
acc.inc();
}
}
public static void main( String[] args ) {
if ( args.length != 1 ) {
System.out.println("java CountRace loops");
System.exit( 1 );
}
long t = System.currentTimeMillis();
int loops = Integer.parseInt( args[0] );
CountAccumulate acc = new CountAccumulate(loops);
Thread t1 = new CountRace( 9000, acc );
Thread t2 = new CountRace( 8000, acc );
t1.start();
Thread.yield();
t2.start();
try {
t1.join(); // wait for t1 to die
t2.join(); // wait for t2 to die
}
catch( InterruptedException ex ) {
}
System.out.println( acc.getCount() + " == " + 17000 );
System.out.println( "overlaps = " + acc.getOverlap() );
t = System.currentTimeMillis() - t;
System.out.println( "elapsed = " + t );
}
}
class CountAccumulate {
private int sum;
private int overlap;
private int loops;
Thread cur;
public CountAccumulate( int loops ) {
this.sum = 0;
this.overlap = 0;
this.cur = null;
this.loops = loops;
}
public synchronized void startOverlap(){
if ( cur != null ) {
overlap++;
}
else {
cur = Thread.currentThread();
}
}
public synchronized void endOverlap(){
Thread c = Thread.currentThread();
if ( c == cur ) {
cur = null;
}
}
public void inc() {
startOverlap();
int s = sum;
for( int i = 0 ; i < loops ; i++ )
; // do nothing
sum = s + 1;
endOverlap();
}
public int getCount() {
return sum;
}
public int getOverlap() {
return overlap;
}
}
mutual exclusion
To avoid the problems from the Race program,
the execution of the inc methods must, when started,
be completed before another thread can invoke inc.
The body of the inc method is called a critical
region, since concurrent execution of the method
can result in corruption of the program.
The synchronized keyword in Java,
means that only one thread is allowed to execute
inc at a time. If another thread attempts to invoke
inc, it will be blocked until the thread executing
inc finishes. Synchronized methods
are guaranteed to be mutually exclusive.
Since synchronizing methods is expensive, it should
be avoided unless it is required.
Any time suspension of a code segment can result
in corruption, the code segment must
be protected by synchronizing the method.
safe race
Only one thread can invoke the inc method at a time.
Any other thread invoking inc will be blocked until
the inc method with the lock returns.
class SafeRace extends Thread {
private int count;
private SafeAccumulate acc;
public SafeRace( int count, SafeAccumulate acc ) {
this.count = count;
this.acc = acc;
}
public void run() {
for( int i = 0; i < count ; i++ ) {
acc.inc();
}
}
public static void main( String[] args ) {
if ( args.length != 1 ) {
System.out.println("java SafeRace loops");
System.exit( 1 );
}
int loops = Integer.parseInt( args[0] );
long t = System.currentTimeMillis();
SafeAccumulate acc = new SafeAccumulate( loops );
Thread t1 = new SafeRace( 9000, acc );
Thread t2 = new SafeRace( 8000, acc );
t1.start();
t2.start();
try {
t1.join();
t2.join();
}
catch( InterruptedException ex ) {
}
System.out.println( acc.getCount() + " == " + 17000 );
t = System.currentTimeMillis() - t;
System.out.println( "elapsed = " + t );
}
}
class SafeAccumulate {
private int sum;
private int loops;
public SafeAccumulate(int loops ) {
this.sum = 0;
this.loops = loops;
}
// the only difference is synchronized
public synchronized void inc() {
int s = sum;
for( int i = 0 ; i < loops ; i++ )
; // do nothing
sum = s + 1;
}
public int getCount() {
return sum;
}
}
synchronized code blocks
Mutual exclusion is guaranteed for the entire body of a
synchronized method. Mutual exclusion can affect
the performance of a program by blocking other
threads from access to the critical code for long periods.
The blocking time can be reduced by synchronizing a
smaller segment of code with:
synchronized( object ) { code }
A synchronized method is identical to placing
a synchronized block around the method's body.
Notice that synchronization is associated with
an object.
class SafeRaceBlock extends Thread {
private int count;
private SafeAccumulateBlock acc;
public SafeRaceBlock( int count, SafeAccumulateBlock acc ) {
this.count = count;
this.acc = acc;
}
public void run() {
for( int i = 0; i < count ; i++ ) {
acc.inc();
}
}
public static void main( String[] args ) {
long t = System.currentTimeMillis();
SafeAccumulateBlock acc = new SafeAccumulateBlock();
Thread t1 = new SafeRaceBlock( 9000, acc );
Thread t2 = new SafeRaceBlock( 8000, acc );
t1.start();
t2.start();
try {
t1.join();
t2.join();
}
catch( InterruptedException ex ) {
}
System.out.println( acc.getCount() + " == " + 17000 );
t = System.currentTimeMillis() - t;
System.out.println( "elapsed = " + t );
}
}
class SafeAccumulateBlock {
private int sum;
public SafeAccumulateBlock() {
sum = 0;
}
// the only difference is synchronized
public void inc() {
synchronized (this ) {
int s = sum;
for( int i = 0 ; i < 10000 ; i++ )
; // do nothing
sum = s + 1;
}
}
public int getCount() {
return sum;
}
}
locks
Each object in Java has a lock.
When a synchronized method or code segment is
entered, an attempt to acquire the lock is made,
if successful, the code can then be executed.
If the lock is not available, then the thread
making the request is blocked, which causes
the thread to be suspended. When the lock
is released by the holding thread, then any blocked threads
are resumed and they can then attempt
to acquire the lock. A queue is normally used
to hold the list of blocked threads, and
the first thread in the queue is unblocked
when the lock is released.
only one synchronized method per object
There is only one lock per object. Consider
the following class and two threads, A and B.
class Critical {
public synchronized void foo() { ... }
public synchronized void bar() { ... }
public void foobar() { ... }
}
-
If thread A invokes foo, then thread B will be blocked
when it attempts to invoke foo or bar.
-
The thread executing in foo can also invoke bar,
since it already has the lock.
-
Any thread can call foobar since it is not synchronized.
Coordinating threads
When writing multi-threaded application, it is common
that coordination of the threads is required
(i.e., thread A must perform some action before thread B).
This is accomplished with the following methods
defined by the Object class:
- wait() - suspend until notified, added to wait queue
- wait( long timeout ) - suspend until notified or timeout expires
- notify() - notify one waiting thread
- notifyAll() - notify all waiting threads
The methods, wait, notify, and notifyAll, can only
be used inside of a synchronized method or code segment.
This is not checked at compile time, but is a runtime
error.
Why is the above not check at compile time?
wait and notify example
Using wait and notify to coordinate thread execution
is shown in the following example.
Use of the java.util.Timer and java.util.TimerTask
is also demonstrated.
The timer method:
schedule(TimerTask task, long delay, long period)
will run TimerTask every period milliseconds
after an initial delay of delay milliseconds.
import java.util.*;
import java.util.TimerTask;
public class WaitNotifyTest {
private int stepNo = 0;
private int maxStep = 0;
// check if the thread can proceed
public synchronized void canProceed() {
// idiom that ensure no false alarms
// waiting should always depend on a condition
while ( stepNo >= maxStep ) {
try {
wait(); // lock is relased by wait
}
catch( InterruptedException ex ) {
// ignore interruption
}
}
stepNo++;
}
// allow a blocked thread to proceed
public synchronized void allowNext() {
maxStep++;
notify(); // notifies only one waiting thread
}
/**
* Demonstrate thread coordination by allowing
* the main thread to proceed when allowed
* by the timer task.
*/
public static void main( String[] args ) {
WaitNotifyTest stepper = new WaitNotifyTest();
NextStepTask advance = new NextStepTask( stepper );
Timer timer = new Timer( true ); // mark as Daemon
timer.schedule( advance, 1000, 2000 );
System.out.println("starting steps");
for( int step = 0; step < 10; step++ ) {
System.out.println("waiting: " + step );
stepper.canProceed();
System.out.println("step: " + step );
}
}
static class NextStepTask extends TimerTask {
private WaitNotifyTest stepper;
public NextStepTask( WaitNotifyTest stepper ) {
this.stepper = stepper;
}
public void run() {
System.out.println("allowing next");
stepper.allowNext();
}
}
}
Normally all threads must exit before the Java program
exits. Any threads marked as daemon will
not prevent the JVM from exiting when all non-daemon
threads have terminated.
canceling a timer
The CountDown class can be replaced with the
java.util.Timer and a class the extends
java.util.TimerTask.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
public class TimerCountDown {
// inner static class
private static class Exit extends TimerTask {
private int count;
public Exit( int count ) {
this.count = count;
}
public void run() {
System.out.println("count: " + count );
count--;
if ( count <= 0 ) {
System.out.println("too late, exiting");
System.exit( 0 );
}
}
}
/**
* The initial thread starts with main.
*/
public static void main( String[] args ) throws IOException {
BufferedReader in
= new BufferedReader(
new InputStreamReader(System.in));
Exit exit = new Exit( 10 );
Timer timer = new Timer("ticker" );
timer.schedule( exit, 1000, 1000 );
String line = null;
while ( (line=in.readLine()) != null ){
if ( line.equals("stop") ) {
exit.cancel();
System.out.println("timer cancelled");
break;
}
else {
System.out.println("what?");
}
}
}
}
Invoking the cancel method on the exit object
removes this TimerTask from the schedule.
fairness
In Java, more than one thread can be blocked on the same X object
with the wait method. When another thread performs
a notify operation on the X object, only one
thread is woken. The notifyAll wakes all threads.
The JVM picks the next runnable thread based
on priorities. If there are any threads with the same
priority then the choice is arbitrary (i.e., there are
no guarantees).
Consider the following class which creates two
CanStep threads that access the same coordination object
(i.e., they will wait on the same object).
import java.util.*;
import java.util.TimerTask;
/**
* Demonstrates the effect of priorities and
* difference between notify and notifyALL.
* The difference must be studied carefully.
*/
public class MultipleThreads {
private int stepNo = 0;
private int maxStep = 0;
private boolean all = false;
public MultipleThreads( boolean all ) {
this.all = all;
}
// check if the thread can proceed
public synchronized void canProceed() {
// idiom that ensure no false alarms
while ( stepNo >= maxStep ) {
try {
wait(); // lock is relased by wait
}
catch( InterruptedException ex ) {
// ignore interruption
}
}
stepNo++;
}
// allow a blocked thread to proceed
public synchronized void allowNext() {
maxStep++;
if ( all ) {
notifyAll();
}
else {
notify();
}
}
public static void main( String[] args ) {
boolean allFlag = false;
int aPriority = 0;
int bPriority = 0;
if ( args.length >= 1 ) {
allFlag = args[0].equals("t");
}
if ( args.length >= 2 ) {
try {
aPriority = Integer.parseInt( args[1] );
}
catch( NumberFormatException ex ) {
// ignore
}
}
if ( args.length >= 3 ) {
try {
bPriority = Integer.parseInt( args[2] );
}
catch( NumberFormatException ex ) {
// ignore
}
}
MultipleThreads stepper = new MultipleThreads( allFlag );
NextStep advance = new NextStep( stepper );
Timer timer = new Timer( true ); // mark as Daemon
timer.schedule( advance, 500, 1000 );
CanStep c1 = new CanStep("A", stepper, 3, aPriority );
CanStep c2 = new CanStep("B", stepper, 5, bPriority );
System.out.println("started A and B");
}
// static inner class, prevents pollution
private static class NextStep extends TimerTask {
private MultipleThreads stepper;
public NextStep( MultipleThreads stepper ) {
this.stepper = stepper;
}
public void run() {
System.out.println("Allow next");
stepper.allowNext();
}
}
// static inner class, prevents pollution
private static class CanStep extends Thread {
private MultipleThreads stepper;
private int limit;
public CanStep(
String name,
MultipleThreads stepper,
int limit,
int priority )
{
super( name );
this.stepper = stepper;
this.limit = limit;
setPriority( NORM_PRIORITY + priority );
start();
}
public void run() {
for( int step = 0; step < limit; step++ ) {
stepper.canProceed();
System.out.println( getName() + " : " + step );
}
}
}
}
starvation of B
Without due care, we can easily create a situation
in which one thread will starve (i.e., never get to
execute). When MultipleThreads is run with:
java MultipleThreads t
The output is:
started A and B
Allow next
A : 0
Allow next
A : 1
Allow next
A : 2
Allow next
B : 0
Allow next
B : 1
Allow next
B : 2
Allow next
B : 3
Allow next
B : 4
Thread B is starved, it only gets to run when
thread A terminates. If A did not terminate,
thread B would never get to run.
The problem is caused by the notifyAll method which wakes
up all threads, and the JVM always picks the A thread
to run.
equal opportunity
Threads A and B get to run only when the notify method is used.
Notify only wakes up one thread, when it blocks again
it is added to the end of the wait queue.
This is demonstrated with the command:
java MultipleThreads f
which produces:
started A and B
Allow next
A : 0
Allow next
B : 0
Allow next
A : 1
Allow next
B : 1
Allow next
A : 2
Allow next
B : 2
Allow next
B : 3
Allow next
B : 4
Studying notifyAll
The Block.doesBlock method
illustrates what happens when multiple threads
are blocked by one object (a Block).
Consider:
class Block {
private boolean block = true;
public synchronized void unblock() {
block = false;
notifyAll();
}
public synchronized void doesBlock( String mesg ) {
while( block ) {
System.out.println( "wait:" + mesg + " " + block );
try {
wait();
}
catch( InterruptedException ex ) {
System.out.println("interrupted: " + mesg );
}
System.out.println( "notified: " + mesg + " " + block );
}
block = true;
}
}
What happens when doesBlock is invoked?
Blocking threads
The BlockingThread.run method invokes
the Block.doesBlock method.
class BlockingThread extends Thread {
private Block block;
private String mesg;
public BlockingThread( String mesg, Block block ) {
this.mesg = mesg;
this.block = block;
}
public void run() {
block.doesBlock( mesg );
}
}
The thread finishes after returning from
Block.doesBlock.
Creating Blocking threads
A single Block object and a set of
BlockingThread threads are created with:
int numThreads = Integer.parseInt( args[0] );
Block block = new Block();
BlockingThread[] threads = new BlockingThread[numThreads];
for ( int i = 0 ; i < numThreads; i++ ) {
String m = "th" + i;
BlockingThread t = new BlockingThread(m, block);
t.start();
threads[ i ] = t;
}
Scanner sc = new Scanner( System.in );
System.out.print("cmd: ");
All the threads will block after each thread invokes
Block.doesBlock.
The initial output when args[0] == "6" is:
wait:th0 true
wait:th1 true
wait:th2 true
wait:th3 true
wait:th4 true
cmd: wait:th5 true
Why is the last line: cmd: wait:th5 true produced?
Testing NotifyAll
Again, the Block.doesBlock is defined as:
public synchronized void doesBlock( String mesg ) {
while( block ) {
System.out.println( "wait:" + mesg + " " + block );
try {
wait();
}
catch( InterruptedException ex ) {
System.out.println("interrupted: " + mesg );
}
System.out.println( "notified: " + mesg + " " + block );
}
block = true;
}
Testing is done by:
Scanner sc = new Scanner( System.in );
System.out.print("cmd: ");
while ( sc.hasNextLine() ) {
String line = sc.nextLine();
if ( line.startsWith("intr" ) ) {
int x = Integer.parseInt( line.split("\\s")[1] );
threads[x].interrupt();
}
else if ( line.startsWith("exit" ) ) {
System.exit( 1 );
}
else {
block.unblock();
}
System.out.print("cmd: ");
}
- What happens when a blank line is entered?
- What happens when intr 3 is entered?
Source for NotifyAll
import java.util.Scanner;
class Block {
private boolean block = true;
public synchronized void unblock() {
block = false;
notifyAll();
}
public synchronized void doesBlock( String mesg ) {
while( block ) {
System.out.println( "wait:" + mesg + " " + block );
try {
wait();
}
catch( InterruptedException ex ) {
System.out.println("interrupted: " + mesg );
}
System.out.println( "notified: " + mesg + " " + block );
}
block = true;
}
}
class BlockingThread extends Thread {
private Block block;
private String mesg;
public BlockingThread( String mesg, Block block ) {
this.mesg = mesg;
this.block = block;
}
public void run() {
block.doesBlock( mesg );
}
}
public class NotifyAll {
public static void main( String[] args ) {
int numThreads = Integer.parseInt( args[0] );
Block block = new Block();
BlockingThread[] threads = new BlockingThread[numThreads];
for ( int i = 0 ; i < numThreads; i++ ) {
String m = "th" + i;
BlockingThread t = new BlockingThread(m, block);
t.start();
threads[ i ] = t;
}
Scanner sc = new Scanner( System.in );
System.out.print("cmd: ");
while ( sc.hasNextLine() ) {
String line = sc.nextLine();
if ( line.startsWith("intr" ) ) {
int x = Integer.parseInt( line.split("\\s")[1] );
threads[x].interrupt();
}
else if ( line.startsWith("exit" ) ) {
System.exit( 1 );
}
else {
block.unblock();
}
System.out.print("cmd: ");
}
}
}
producer/consumer
One of the most common forms of cooperating
threads is the producer/consumer pair.
The producer generates data that the consumer
uses the data. The producer and consumer each run in
their own thread. The producer should only
produce as fast as the consumer can consume.
The unix pipe command,
cmd1 | cmd2
creates a producer consumer pair, the
first command's output is sent to
the second command's standard input.
The producer is suspended if the consumer
cannot keep up.
The consumer is suspended if it attempts to
consume too much.
link
The Link interface will be used to connect
the producer to the consumer. By defining
an interface, we are free to provide
different implementations.
/**
* Object o is send via the Link with send,
* recv is used to retrieve the object,
* close indicates that no more objects will be
* sent via the link.
* The number of object stored in the link is given
* by getSize().
*/
interface Link {
void send( Object o );
void close();
Object recv();
int getSize();
}
Producer
The producer will generate data and send it via
the link to the consumer. The link
must block the producer if the consumer does not
keep up.
public class Producer extends Thread {
private Link link;
private int totalItems;
private boolean flush;
public Producer( Link link, int totalItems, boolean flush ) {
this.link = link;
this.totalItems = totalItems;
this.flush = flush;
}
public void run() {
for( int items = 0 ; items < totalItems; items++ ) {
Integer i = new Integer( items);
System.out.println("sending: " + i );
if ( flush ) System.out.flush();
link.send( i );
System.out.println( "sent: " + i + " size: "
+ link.getSize() );
if ( flush ) System.out.flush();
}
link.close();
}
}
Consumer
The consumer thread will attempt to retrieve objects
from the link. The link should block if there is
no available objects.
public class Consumer extends Thread {
private Link link;
public Consumer( Link link ) {
this.link = link;
}
public void run() {
while( true ) {
do {
Object o = link.recv();
if ( o == null ) return;
Integer i = (Integer)o;
System.out.println("recv: " + i );
} while ( link.getSize() != 0 );
try {
Thread.sleep( 10 );
}
catch( InterruptedException e ) {
}
}
}
}
produce/consumer test
Test the produce/consumer pair by sending
a fixed number of items, before closing
the link. The usage is:
java TestProdConsum num_items link_size
where link_size is the size of the buffer in the
link and num_items is the number of items to produce.
public class TestProdConsum {
public static void main( String[] args ) {
if ( args.length != 3 ) {
System.out.println("usage: java TestProdConsum prods buf flush");
System.exit( 1 );
}
try {
int products = Integer.parseInt( args[0] );
int bufSize = Integer.parseInt( args[1] );
boolean flush = args[2].equals("flush");
Link link = new Bounded( bufSize );
Producer prod = new Producer( link, products, flush );
Consumer consum = new Consumer( link );
consum.start();
prod.start();
}
catch( Exception e ) {
e.printStackTrace();
}
}
}
bounded buffer implementation
A fixed size bounded buffer is:
public class Bounded implements Link {
private Object[] buffer;
private int head, tail, len;
private boolean closed;
public Bounded( int size ) {
buffer = new Object[size];
head = tail = 0;
len = 0;
closed = false;
}
public synchronized void send( Object o ) {
while ( len >= buffer.length ) {
try {
wait();
}
catch( InterruptedException ex ) {
}
}
buffer[ head ] = o;
head = ( head + 1 ) % buffer.length;
len++;
notify();
}
public synchronized Object recv() {
if ( len == 0 && closed )
return null;
while ( len == 0 ) {
try {
wait();
}
catch( InterruptedException ex ) {
}
if ( len == 0 && closed ) return null;
}
Object o = buffer[tail];
tail = (tail+1) % buffer.length;
len--;
notify();
return o;
}
public synchronized void close() {
closed = true;
notify();
}
public synchronized int getSize() {
return len;
}
}
deadlock
Deadlock occurs when two or more threads attempt
to lock resources already claimed by other threads.
If thread t1 has resource r1 and thread t2 has resource
r2 and t1 attempts to lock r2, while t2 is trying to
lock r1, then the threads are deadlocked. These
thread are now stuck since they will not release
any resource until they get a resource which is already
locked. The classic deadlock is shown by:
public class DeadLock extends Thread {
private Object res1, res2;
public DeadLock( String name, Object res1, Object res2 ) {
super( name );
this.res1 = res1;
this.res2 = res2;
}
public void run() {
// lock res1
synchronized( res1 ) {
System.out.println( getName() + ": locked " + res1 );
try {
Thread.sleep( 100 );
}
catch( InterruptedException ex ) {
}
// now try to get res2
synchronized( res2 ) {
System.out.println( getName() + ": locked " + res2 );
}
}
}
public static void main( String[] args ) {
Object r1 = new Object();
Object r2 = new Object();
DeadLock t1 = new DeadLock("t1", r1, r2);
DeadLock t2 = new DeadLock("t2", r2, r1);
t1.start();
t2.start();
Thread[] threads = new Thread[10];
int num = Thread.enumerate( threads );
for( int i = 0 ; i < num; i++ ) {
System.out.println( threads[i] );
}
}
}
What simple change would elimate the deadlock from
the above code?
Deadlock that takes awhile
The Deadlock class will always deadlock since
the sleep force t1 to get r1 and t2 to get r2
before the threads attempt to get the next resource.
DeadLock1 shows a more realistic situation
where the deadlock will eventual happen,
but when is not known.
This deadlock occurs when a thread has captured
res1 but is suspended before it captures res2.
The second thread can now capture res2, and
now can not get res1.
public class DeadLock1 extends Thread {
private Object res1, res2;
public DeadLock1( String name, Object res1, Object res2 ) {
super( name );
this.res1 = res1;
this.res2 = res2;
}
public void run() {
// lock res1
synchronized( res1 ) {
System.out.println(getName() + ": locked " + res1);
// now try to get res2
synchronized( res2 ) {
System.out.println(getName() + ": locked " + res2);
}
}
}
public static void main( String[] args ) throws Exception {
while( true ) {
Object r1 = new Object();
Object r2 = new Object();
DeadLock1 t1 = new DeadLock1("t1", r1, r2);
DeadLock1 t2 = new DeadLock1("t2", r2, r1);
t1.start();
t2.start();
// wait for t1 and t2 to die before restarting
t1.join();
t2.join();
}
}
}
multi-processing with threads
The sum program is given as input a directory containing
1000 files. Each file contains 10000 integers, each integer is
on a separate line. The sum program should produce the total sum
of all the integers in all the files with a set of threads.
The program should contain the following threads:
-
A master thread that reads all the filenames of
the files containing the integers. The master
thread will then start a set of worker
threads. The master thread then provides a
filename (a work assignment) to any worker
thread that request one of the files. The master
should keep track of the total number of work assignments.
The master is also responsible for receiving the
totals of each individual file. Receipt of each sum allows
the master to reduce the number of outstanding
work items. The master thread should print out
the total sum and terminate the program when all work items
are returned.
-
A worker thread is responsible for requesting
a filename from the single master thread. The
worker should then open the file and read its
contents to produce the sum of integers in the file.
The sum should then be sent back to the master.
The worker should then request a new file name from
the master. If all the filenames have been handed
out the worker thread should terminate.
worker thread
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
/**
* Request a file to work on. The integers contained in the file are totaled.
* The total is reported back to the controller thread.
*/
public class Worker extends Thread {
Controller controller;
public Worker( Controller controller ) {
this.controller = controller;
}
public void run() {
File f = null;
while( (f=controller.getFile()) != null ) {
int sum = 0;
try {
FileReader frd = new FileReader( f );
BufferedReader in = new BufferedReader( frd );
String line = null;
while( (line=in.readLine()) != null ) {
int value = 0;
try {
value = Integer.parseInt( line );
}
catch( NumberFormatException ex ) {
// ignore lines with errors
}
sum += value;
}
in.close();
}
catch( IOException ex ) {
}
controller.updateSum( sum );
}
}
}
controller thread
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Iterator;
import java.io.File;
import java.io.IOException;
/**
* The controller class creates a set of worker
* threads to calculate * the sum of a set of
* integers contained in files in the sepecified
* directory.
*/
public class Controller {
private String directoryPath;
private int sum;
private ArrayList workers;
private LinkedList files;
/**
* Start a set of worker threads to some the integers
* found in the directoryPath directory.
*/
public Controller( String directoryPath, int noWorkers ) {
this.directoryPath = directoryPath;
this.sum = 0;
this.workers = new ArrayList();
this.files = new LinkedList();
File dir = new File( directoryPath );
File[] list = dir.listFiles();
if ( list == null ) {
return;
}
/*
* files must be created before any workers
* are started.
*/
for( int i = 0 ; i < list.length; i++ ) {
files.add( list[i].getName() );
}
/* create the workers */
for( int i = 0 ; i < noWorkers ; i++ ) {
workers.add( new Worker( this ) );
}
/* start them up */
Iterator it = workers.iterator();
while ( it.hasNext() ) {
Worker w = (Worker)it.next();
w.start();
}
}
/**
* Retrieve the next file to processes.
* Must be synchronized since more than one worker
* can attempt to get a file.
*/
public synchronized File getFile() {
if ( files.size() == 0 ) return null;
String f = (String)files.removeFirst();
return new File( directoryPath, f );
}
/**
* Invoked by a worker to update the total sum.
* Must be synchronized since more than one worker
* can attempt to upate the sum.
*/
public synchronized void updateSum( int s ) {
sum += s;
}
public void waitForWorkers() {
Iterator it = workers.iterator();
while ( it.hasNext() ) {
Worker w = (Worker)it.next();
try {
w.join();
}
catch( InterruptedException ex ) {
}
}
}
public synchronized int getSum() {
return sum;
}
public static void main( String[] args ) {
if ( args.length != 2 ) {
System.err.println(
"uasge: java Controller dir no_workers");
return;
}
long t0 = System.currentTimeMillis();
int noWorkers = 0;
try {
noWorkers = Integer.parseInt( args[1] );
}
catch( NumberFormatException ex ) {
System.err.println("no_workers is not a number");
return;
}
Controller controller =
new Controller( args[0], noWorkers );
controller.waitForWorkers();
long t1 = System.currentTimeMillis();
System.out.println("sum = " + controller.getSum() );
System.out.println("time = " + (t1-t0) );
}
}
bouncing ball
import java.awt.GridLayout;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Dimension;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Random;
public class BallArena extends Component {
private ArrayList balls;
public BallArena() {
balls = new ArrayList();
balls.add(new BouncingBall(0.3,0.3,0.1, Color.red));
balls.add(new BouncingBall(0.7,0.1,0.07, Color.green));
balls.add(new BouncingBall(0.5,0.5,0.05, Color.blue));
balls.add(new BouncingBall(0.1,0.1,0.15, Color.yellow));
}
public List getBalls() {
return balls;
}
/**
* paint draws the bouncing balls.
*/
public void paint( Graphics g ) {
Dimension sz = getSize();
Iterator it = balls.iterator();
while ( it.hasNext() ) {
BouncingBall ball = (BouncingBall)it.next();
ball.draw( g, sz );
}
}
private static class MoveBalls extends TimerTask {
private BallArena arena;
public MoveBalls( BallArena arena ) {
this.arena = arena;
}
public void run() {
List l = arena.getBalls();
Iterator it = l.iterator();
while ( it.hasNext() ) {
BouncingBall ball = (BouncingBall)it.next();
ball.update();
}
/* causes the paint method to be invoked */
arena.repaint();
}
}
private static void dumpThreads( String msg ) {
System.out.println();
System.out.println( msg );
Thread[] threads = new Thread[100];
int num = Thread.enumerate( threads );
for( int i = 0 ; i < num; i++ ) {
System.out.println( threads[i] );
}
}
public static void main( String[] args ) {
BallArena.dumpThreads("start");
Timer timer = new Timer( true );
BallArena.dumpThreads("after timer");
Frame frame = new Frame();
BallArena.dumpThreads("after frame");
/* static method inner class */
frame.addWindowListener (
new WindowAdapter() {
public void windowClosing( WindowEvent e ) {
System.exit( 0 );
}
}
);
frame.setTitle( "Bouncing Balls" );
frame.setLayout( new GridLayout(1,1) );
BallArena ba = new BallArena();
frame.add ( ba );
frame.setBackground( Color.white );
frame.pack();
frame.setSize( 400, 400 );
frame.show();
timer.schedule( new MoveBalls( ba ), 100, 100 );
BallArena.dumpThreads("main end");
}
}
/**
* Describes and controls the behaviour of a bouncing ball.
*/
class BouncingBall {
private static Random rand = new Random();
private double x, y, r;
private double dx, dy;
private Color colour;
private final double SCALE = 8.0;
public BouncingBall( double x, double y, double r, Color c ) {
this.x = x;
this.y = y;
this.r = r;
this.colour = c;
this.dx = (rand.nextDouble()-0.5) / SCALE;
this.dy = (rand.nextDouble()-0.5) / SCALE;
}
/* invoked by awt thread */
public synchronized void draw( Graphics g, Dimension sz ) {
int w = sz.width;
int h = sz.height;
int m = ( w < h ) ? w : h;
int ix = (int)(w*x);
int iy = (int)(h*y);
int ir = (int)(m*r);
int ir2 = ir/2;
g.setColor( colour );
g.fillOval( ix-ir2, iy-ir2, ir, ir );
}
/* invoked by timer thread */
public synchronized void update() {
double tx = x + dx;
if ( tx < 0.0 ) {
dx = rand.nextDouble() / (2.0*SCALE);
x += dx;
}
else if ( tx > 1.0 ) {
dx = -rand.nextDouble() / (2.0*SCALE);
x += dx;
}
else {
x = tx;
}
double ty = y + dy;
if ( ty < 0.0 ) {
dy = rand.nextDouble() / (2.0*SCALE);
y += dy;
}
else if ( ty > 1.0 ) {
dy = -rand.nextDouble() / (2.0*SCALE);
y += dy;
}
else {
y = ty;
}
}
}
Scheduling
- Preemptive - thread switches at any time
- Nonpreemptive - switches only when another thread yields
Consider the following:
class Ticking extends Thread {
private Ticking[] threads;
private int id;
private int limit;
private int interval;
private int inc;
public Ticking(
Ticking[] threads, int id,
int limit, int interval, int pri )
{
this.threads = threads;
this.id = id;
this.limit = limit;
this.interval = interval;
setPriority( pri );
}
public int getInc() {
return inc;
}
private int spin() {
int loops = 1000;
while ( loops > 0 ) {
loops--;
}
return loops;
}
public void run() {
for( inc = 0 ; inc < limit; inc++ ) {
if ( ( inc % interval) == 0 ) {
System.out.print( "thread: " + id + " :");
for( int i = 0 ; i < threads.length; i++ ) {
System.out.print( " " + threads[i].getInc() );
}
System.out.println();
}
spin();
}
}
}
public class Scheduling {
public static void main( String[] args ) {
final int limit = 100000;
final int interval = 10000;
Ticking[] threads = new Ticking[ args.length ];
for( int i = 0 ; i < args.length; i++ ) {
int pri = Integer.parseInt( args[i] );
Ticking t =
new Ticking(threads, i, limit, interval, pri );
System.out.println( i + "/" + t.getPriority() );
threads[i] = t;
}
for( int i = 0 ; i < args.length; i++ ) {
threads[i].start();
}
}
}
thread state summary
Identify one unsafe operation in the above code?
Thread API summary
The most important methods of a thread object are:
- void run()
-
The main method to execute when the thread starts.
- String getName()
-
Return the name of the thread.
- int getPriority()
-
Return the priority from 1 (MIN) to 10 (MAX).
- void setPriority(int p )
-
Change the priority to p.
- void interrupt()
-
Only guaranteed to interrupt a thread that is
sleeping or waiting.
- void join()
-
Block current thread until the join thread dies.
- void start()
-
Make the thread runnable. The JVM can now schedule it.
- void sleep( long mills )
-
Sleep for mills milliseconds. Can be interrupted.
- void yield()
-
Ask the JVM to schedule another thread. The same thread
could be restarted.
The following methods should be avoided: suspend, resume, stop.