CS360 Lecture 12
Multithreading
Thursday, March 11, 2004
Multithreading: Chapter 16
At any
time, a thread can be in one of several thread states:
-
Born: this is the time when the thread is
created.
SimpleThread first = new SimpleThread(
5 );
-
Ready
(runnable): a
thread transitions to the ready state once the start method is called:
first.start();
-
Running: a thread transitions to the running
state once the operating system allocates a processor to the thread.
-
Dead: A thread is dead once itŐs run
method has completed, or itŐs terminated by an uncaught exception
-
Blocked: A thread becomes blocked when it
attempts to perform a task that cannot be completed immediately. The task is
waiting for something outside of the code, for example I/O. Once the task can
resume, the thread returns to the ready state and waits for processor time.
-
Waiting: This happens because while
executing the code, the thread comes across the wait method. It remains in the wait
status until the either method notify or notifyAll is
called.
-
Sleeping: A thread can go to sleep for a
specified time. It transitions to the ready state once that time has passed.
The job of
the scheduler is to make sure that the thread with the highest priority is
running at all times. The thread scheduler also ensures that threads of the
same priority are executed in a round-robin fashion. Each thread gets a
specific quantum of time.
Higher
priority threads could postpone the execution of lower priority threads
indefinitely. Starvation!
If there
are several threads of equal priority, one thread could yield to give other
threads a chance to run. This is only useful in non-timesliced systems, where
one thread could execute continuously until it completes execution. The thread
yields by calling the method yield.
public class YieldThread extends Thread
{
public
void run()
{
for ( int count = 0; count < 4; count++)
{
System.out.println( count + "
From: " + getName() );
yield();
}
}
public
static void main( String[] args )
{
YieldThread first = new YieldThread();
YieldThread second = new YieldThread();
first.setPriority( 1);
second.setPriority( 1);
first.start();
second.start();
System.out.println( "End" );
}
}
End
0 From: Thread-1
0 From: Thread-2
1 From: Thread-1
1 From: Thread-2
2 From: Thread-1
2 From: Thread-2
3 From: Thread-1
3 From: Thread-2
Threads are
created in Java by extending the class Thread. This class contains several
useful methods:
-
interrupt
():
Interrupts
this thread.
-
sleep
(long millis)
: Causes the currently executing
thread to sleep (temporarily cease execution) for the specified number of
milliseconds.
-
yield
()
: Causes the currently executing
thread object to temporarily pause and allow other threads to execute.
-
destroy
():
Destroys this thread, without any
cleanup.
-
wait():
Causes current thread to wait until either another thread invokes the notify()
method or the notifyAll()
method for this object, or a specified amount of time has elapsed.
public class ThreadTester {
public static void main( String [] args )
{
PrintThread thread1 = new
PrintThread( "thread1" );
PrintThread thread2 = new
PrintThread( "thread2" );
PrintThread thread3 = new PrintThread(
"thread3" );
System.err.println( "Starting
threads" );
thread1.start();
thread2.start();
thread3.start();
System.err.println( "Threads
started, main ends\n" );
}
}
class PrintThread extends Thread {
private int sleepTime;
public PrintThread( String name )
{
super( name );
sleepTime = ( int ) ( Math.random()
* 5001 );
}
public void run()
{
try {
System.err.println(
getName() + " going to sleep for " +
sleepTime );
Thread.sleep(
sleepTime );
}
catch ( InterruptedException
exception ) {
exception.printStackTrace();
}
System.err.println( getName() +
" done sleeping" );
}
}
Starting threads
Threads started, main ends
thread1 going to sleep for 3440
thread2 going to sleep for 3064
thread3 going to sleep for 4035
thread2 done sleeping
thread1 done sleeping
thread3 done sleeping
Starting threads
Threads started, main ends
thread1 going to sleep for 4571
thread2 going to sleep for 2209
thread3 going to sleep for 375
thread3 done sleeping
thread2 done sleeping
thread1 done sleeping
Starting threads
Threads started, main ends
thread1 going to sleep for 1183
thread2 going to sleep for 1834
thread3 going to sleep for 469
thread3 done sleeping
thread1 done sleeping
thread2 done sleeping
Thread synchronization is needed for those times when multiple threads have access to the same object.
Synchronization is achieved through the use of monitors. These lock objects when they are being manipulated by a thread.
LetŐs look at what happens if we donŐt have synchronization.
public class
SharedBufferTest {
public static void main(
String [] args )
{
Buffer sharedLocation = new UnsynchronizedBuffer();
Producer producer = new Producer( sharedLocation );
Consumer consumer = new Consumer( sharedLocation );
producer.start();
consumer.start();
}
}
public class
UnsynchronizedBuffer implements Buffer {
private int buffer = -1;
public void set( int value )
{
System.err.println(Thread.currentThread().getName() +
" writes " + value );
buffer = value;
}
public int get()
{
System.err.println(Thread.currentThread().getName() +
" reads " + buffer );
return buffer;
}
}
public class
Producer extends Thread {
private Buffer sharedLocation;
public Producer( Buffer shared )
{
super(
"Producer" );
sharedLocation = shared;
}
public void run()
{
for ( int count
= 1; count <= 4; count++ ) {
try {
Thread.sleep(( int )( Math.random() * 3001 ) );
sharedLocation.set( count );
}
catch ( InterruptedException exception ) {
exception.printStackTrace();
}
}
System.err.println( getName() + " done producing." +
"\nTerminating
" + getName() + ".");
}
}
public class
Consumer extends Thread {
private Buffer sharedLocation;
public Consumer( Buffer shared )
{
super(
"Consumer" );
sharedLocation
= shared;
}
public void run()
{
int sum = 0;
for ( int count
= 1; count <= 4; count++ ) {
try {
Thread.sleep(( int )( Math.random() * 3001 ) );
sum += sharedLocation.get();
}
catch ( InterruptedException exception ) {
exception.printStackTrace();
}
}
System.err.println( getName()
+
" read values totaling: " + sum + ".\nTerminating " +
getName()
+
".");
}
}
public interface
Buffer {
public void set( int value
);
public int get();
}
Consumer reads -1
Consumer reads -1
Producer writes 1
Consumer reads 1
Producer writes 2
Consumer reads 2
Consumer read values
totaling: 1.
Terminating
Consumer.
Producer writes 3
Producer writes 4
Producer done
producing.
Terminating
Producer.
SharedBufferTest
Consumer reads -1
Producer writes 1
Consumer reads 1
Producer writes 2
Producer writes 3
Consumer reads 3
Producer writes 4
Producer done
producing.
Terminating
Producer.
Consumer reads 4
Consumer read values
totaling: 7.
Terminating
Consumer.
The following example uses the same Consumer and Producer threads as in the last example. However, this time we avoid the errors produced in the previous example.
public class SharedBufferTest2 {
public
static void main( String [] args )
{
SynchronizedBuffer sharedLocation = new
SynchronizedBuffer();
StringBuffer columnHeads = new StringBuffer(
"Operation\t\t"
);
columnHeads.setLength( 40 );
columnHeads.append( "Buffer\t\tOccupied Count" );
System.err.println( columnHeads );
System.err.println();
sharedLocation.displayState( "Initial State" );
Producer producer = new Producer( sharedLocation );
Consumer consumer = new Consumer( sharedLocation );
producer.start();
consumer.start();
}
}
public class SynchronizedBuffer implements Buffer {
private int
buffer = -1;
private int
occupiedBufferCount = 0;
public
synchronized void set( int value )
{
String name = Thread.currentThread().getName();
while ( occupiedBufferCount == 1 ) {
try {
System.err.println( name +
" tries to write." );
displayState( "Buffer full. " + name +
" waits." );
wait();
}
catch (
InterruptedException exception ) {
exception.printStackTrace();
}
}
buffer = value;
++occupiedBufferCount;
displayState( name + " writes " + buffer );
notify();
}
public
synchronized int get()
{
String name = Thread.currentThread().getName();
while ( occupiedBufferCount == 0 ) {
try {
System.err.println( name + " tries to read." );
displayState( "Buffer empty. " + name
+ " waits." );
wait();
}
catch (
InterruptedException exception ) {
exception.printStackTrace();
}
}
--occupiedBufferCount;
displayState( name + " reads " + buffer );
notify();
return buffer;
}
public void
displayState( String operation )
{
StringBuffer outputLine = new StringBuffer(
operation );
outputLine.setLength( 40 );
outputLine.append( buffer + "\t\t"
+ occupiedBufferCount
);
System.err.println( outputLine );
System.err.println();
}
}
Operation
Buffer Occupied
Count
Initial State -1
0
Consumer tries to read.
Buffer empty.
Consumer waits. -1
0
Producer writes 1 1
1
Consumer reads 1 1
0
Consumer tries to read.
Buffer empty.
Consumer waits. 1
0
Producer writes 2 2
1
Consumer reads 2 2
0
Producer writes 3 3 1
Consumer reads 3 3
0
Consumer tries to read.
Buffer empty.
Consumer waits. 3
0
Producer writes 4 4
1
Producer done producing.
Terminating Producer.
Consumer reads 4 4
0
Consumer read values totaling: 10.
Terminating Consumer.