lecture08 (Multi-Threading) - SLATE

cavalcadejewelSoftware and s/w Development

Nov 18, 2013 (3 years and 11 months ago)

95 views

Mulithreading

Goals


To understand how multiple threads can execute
in parallel


To learn how to implement threads


To understand race conditions and deadlocks


To be able to avoid corruption of shared objects
by using synchronized methods


Multitasking


Several
processes

running at same time, even if only
one processor

Multitasking in C/C++


Multiple tasks (processes) are started by making
a
system call

to the operating system which
creates a new process on behalf of the original
program.


OS will then allocate processor time to each
process running on the machine switching
between them.


Have to write different code for each operating
systems so difficult to port.

Multitasking in Java


In Java we can achieve an equivalent effect by
creating multiple
threads


Threads are like the processes, but are
light
-
weight
, that is, they do not require as much
overhead and operating system resources as full
blown processes

Thread & Multithreading


A single sequential flow of control
within a program.


A program unit that is executed
independently

of other parts of the
program


Inside a process, can have several
threads called
multithreading


Each thread represents its own flow
of logic, gets
separate

runtime stack

Multithreading in Java


The JVM (a process) executes each thread in
the program for a short amount of time


Threads are largely
managed by the VM


As everything in Java belongs to a class,
threading is integrated into objects


New threads are created when objects are
started in their own thread


Same code for every Java VM


Simpler than in most other languages


Running Threads


There are two techniques for running a run a
thread:

1.
Subclassing
Thread

class and overriding
run

method

2.
Implementing
Runnable

interface


The
Thread

class implements a generic thread
that, by default, does nothing

Subclassing
Thread

class

class ThreadTest extends Thread

{



public ThreadTest(String name) {


super(name);


System.out.println(“Created “ + name);


}



public void run() {


System.out.println(getName()+“ started");


try {


Thread.sleep((long)(Math.random() * 1000));


} catch (InterruptedException e) { }


System.out.println(getName() + “ finishing");


}


}

public class ThreadDemo {


public static void main(String args[]) {


ThreadTest[] tt = new ThreadTest[3];


tt[0] = new ThreadTest("Thread 1");


tt[1] = new ThreadTest(“Thread 2”);


tt[2] = new ThreadTest(“Thread 3”);


System.out.println(“Finished creating threads");



tt[0].start();


tt[1].start();


tt[2].start();


System.out.println(“Finished starting threads");


}

}

> java ThreadDemo

Created Thread 1

Created Thread 2

Created Thread 3

Finished creating threads

Thread 1 started

Thread 2 started

Thread 3 started

Finished starting threads

Thread 3 finishing

Thread 1 finishing

Thread 2 finishing

Implementing
Runnable

Interface

class ThreadTest2 implements Runnable {


String name;


public ThreadTest2(String name) {


this.name = name;


System.out.println(“Created “ + name);


}



public void run() {


System.out.println(name + “ started");


try {


Thread.sleep((long)(Math.random() * 1000));


} catch (InterruptedException e) { }


System.out.println(name + “ finishing");


}


}

public class ThreadDemo2 {


public static void main(String args[]) {


Runnable[] tt = new ThreadTest2[3];


tt[0] = new ThreadTest2("Thread 1");


tt[1] = new ThreadTest2(“Thread 2”);


tt[2] = new ThreadTest2(“Thread 3”);


System.out.println(“Finished creating threads");



new Thread(tt[0]).start();


new Thread(tt[1]).start();


new Thread(tt[2]).start();


System.out.println(“Finished starting threads");


}

}

The Life Cycle of a Thread

New Thread State


After creating an instance
Thread
, the thread is
in new thread state

tt[0] = new ThreadTest("Thread 1");


When a thread is a New Thread, it is merely an
empty Thread object; no system resources have
been allocated for it yet.


In this state, you can only
start

the thread.


Calling any method besides start when a thread
is in this state makes no sense and causes an
IllegalThreadStateException
.

Running Thread State


After invoking
start

method on Thread, the
thread is in running state

tt[0].start();


The start method creates the system resources
necessary to run the thread, schedules the
thread to run, and calls the thread's run method


Thread Scheduler


The thread scheduler runs each thread for a
short amount of time called a
time slice



Then the scheduler picks another thread from
those that are runnable


A thread is runnable if it is not asleep or blocked
in some way


There is no guarantee about the order in which
threads are executed


Thread Not Runnable


A thread becomes Not Runnable when one of
these events occurs:


Its sleep method is invoked


The thread calls the wait method to wait for a specific
condition to be satisifed


The thread is blocking on I/O



For each entrance into the Not Runnable state,
there is a specific and distinct escape route that
returns the thread to the Runnable state

Escape Routes


If a thread has been put to sleep, then the
specified number of milliseconds must elapse




If a thread is waiting for a condition, then another
object
must
notify the waiting thread of a change
in condition by calling
notify

or
notifyAll




If a thread is blocked on I/O, then the I/O must
complete.

Stoping a Thread


A thread terminates when its run method returns


Do not terminate a thread using the deprecated stop
method


Instead, notify a thread that it should terminate

t.interrupt();


A thread's run method should check occasionally
whether it has been interrupted


Use the
isInterrupted

method


An interrupted thread should release resources, clean up,
and exit


The sleep method throws an
InterruptedException

when a sleeping thread is interrupted


Catch the exception


Terminate the thread

Example

public void run() {


try {


for (int = 1; i <= REPETITIONS &&






!isInterrupted()
; i++) {


//do the work


}


} catch (InterruptedException ex) {



// handle or ignore


}


//cleanup

}

Synchronizing Threads


When threads share a common object, they can
conflict with each other.


In this example, a
DepositThread

and a
WithdrawThread

both manipulate a
single

BankAccount


run

Method of
DepositThread

public void run() {


try {



for (int i = 1; i <= REPETITIONS &&






!isInterrupted(); i++){




account.deposit(amount);




sleep(DELAY);



}


} catch (InterruptedException ex) {


}

}

Sample Application


Create a BankAccount object


Create a DepositThread t0 to deposit $100 into
the account for 10 iterations


Create a WithdrawThread t1 to withdraw $100
from the account for 10 iterations


The result should be zero, but sometimes it is
not


BankAccount.java

public class BankAccount {


private double balance;



public BankAccount() {



balance = 0;


}



public void deposit(double amount) {


System.out.print("Depositing " + amount);


double newBalance = balance + amount;


System.out.println(", new balance is " +









newBalance);


balance = newBalance;


}

BankAccount.java




public void withdraw(double amount) {


System.out.print("Withdrawing " + amount);



double newBalance = balance
-

amount;


System.out.println(", new balance is " +









newBalance);


balance = newBalance;


}



public double getBalance() {


return balance;


}

}

DepositThread.java

class DepositThread extends Thread {


public DepositThread(BankAccount anAccount,







double anAmount) {


account = anAccount;


amount = anAmount;


}


public void run() {


try {


for (int i = 1; i <= REPETITIONS &&







!isInterrupted();i++) {


account.deposit(amount); sleep(DELAY);



}


} catch (InterruptedException exception) { }


}


private BankAccount account;


private double amount;


private static final int REPETITIONS = 10;


private static final int DELAY = 10;

}

WithdrawThread.java

class WithdrawThread extends Thread {


public WithdrawThread(BankAccount anAccount, double
anAmount) {


account = anAccount;


amount = anAmount;


}


public void run() {


try {


for (int i = 1; i <= REPETITIONS &&






!isInterrupted(); i++) {


account.withdraw(amount); sleep(DELAY);



}


} catch (InterruptedException ex) { }


}


private BankAccount account;


private double amount;


private static final int REPETITIONS = 10;


private static final int DELAY = 10;

}

Scenario to Explain Non
-
zero Result


The first thread t0 executes the lines

System.out.print("Depositing " + amount);

double newBalance = balance + amount;



t0 reaches the end of its time slice and t1 gains
control


t1 calls the withdraw method which withdraws
$100 from the balance variable.


Balance is now
-
100


t1 goes to sleep

Scenario to Explain Non
-
zero Result


t0 regains control and picks up where it left off.


t0 executes the lines

System.out.println(", new balance is " +
newBalance);

balance = newBalance;


The balance is now 100 instead of 0 because
the deposit method used the OLD balance


This is called a race condition.

Race condition


Occurs if the effect of multiple threads on shared
data depends on the order in which the threads
are scheduled


It is possible for a thread to reach the end of its
time slice in the middle of a statement.


It may evaluate the right
-
hand side of an
equation but not be able to store the result until
its next turn.


Solving the Race Condition Problem


A thread must be able to lock an object
temporarily


When a thread has the object locked, no other
thread can modify the state of the object.


In Java, use synchronized methods to do this


Tag all methods that contain thread
-
sensitive
code with the keyword
synchronized



Synchronized Methods

public class BankAccount {


public
synchronized

void deposit(







double amount) {


. . .


}



public
synchronized

void withdraw(







double amount) {


. . .


}


. . .

}

Synchronized Methods


By declaring both the deposit and withdraw
methods to be synchronized


Our program will run correctly


Only one thread at a time can execute either method on a
given object


When a thread starts one of the methods, it is
guaranteed to execute the method to completion before
another thread can execute a synchronized method on
the same object.



By executing a synchronized method:


The thread acquires the object lock.


No other thread can acquire the lock.


No other thread can modify the state of the object until
the first thread is finished


Visualization of Synchronized
Thread Behavior


Imagine the object is a restroom that only one
person can use at a time


The threads are people


If the restroom is empty, a person may enter


If a second person finds the restroom locked, the
second person must wait until it is empty


If multiple people want to gain access to the
restroom , they all wait outside


The people may not form an orderly queue;


A randomly chosen person may gain access
when the restroom becomes available again