CS865 – Distributed Software Development |
Threads |
Based on:
See Also
http://www.particle.kth.se/~lindsey/JavaCourse/Book/courseMap.html
Concurrent Processing
Contemporary
OS - multiple processes appear to execute concurrently on a
machine via timesharing resources.
Concurrent processing within a process
It is
often useful for a process to have parallel threads of execution, each of which
timeshare the system resources in much the same way as concurrent processes.
Java Threads
What are threads?
The Java
Virtual Machine allows an application to have multiple threads of execution
running concurrently.
Java provides
a Thread class:
public class Thread
extends Object
implements Runnable
·
When a Java Virtual Machine starts up, there is usually a
single thread (which typically calls the method named main of some designated
class).
·
The Java Virtual Machine continues to execute threads until
either of the following occurs:
o
The exit method of class Runtime has been called and the
security manager has permitted the exit operation to take place.
o
All threads have terminated, either by returning from the
call to the run method or by throwing an exception that propagates beyond the
run method.
Two ways to
create a new thread of execution
1. Using a subclass of
the Thread class
2. Using a class that
implements the Runnable interface
Create a class that is a subclass of the
Thread class
Declare a
class to be a subclass of Thread. This subclass should override the run
method of class Thread. An instance of the subclass can then be allocated and
started:
Create a class that is a subclass of the Thread
class
/****************************************************** * A program which makes
use of the SomeThread class. * M.
Liu *****************************************************/ // import SomeThread; public class RunThreads { public static void main
(String[] args) { SomeThread p1 = new SomeThread(1); p1.start(); SomeThread p2 = new SomeThread(2); p2.start(); SomeThread p3 = new SomeThread(3); p3.start(); } }// end class RunThreads |
/************************************************** * A class which extends
the Java Thread class. * M.
Liu *************************************************/ class SomeThread extends Thread { int myID; SomeThread(int id) { this.myID = id; } public void run() { int i; for (i = 1; i < 11; i++)
System.out.println ("Thread"+myID
+ ": " + i); } } //end class SomeThread |
Sample Runs |
|
Thread1: 1 Thread1: 2 Thread1: 3 Thread1: 4 Thread1: 5 Thread1: 6 Thread1: 7 Thread1: 8 Thread1: 9 Thread2: 1 Thread1: 10 Thread3: 1 Thread2: 2 Thread3: 2 Thread2: 3 Thread3: 3 Thread2: 4 Thread3: 4 Thread2: 5 Thread3: 5 Thread2: 6 Thread3: 6 Thread2: 7 Thread3: 7 Thread2: 8 Thread2: 9 Thread2: 10 Thread3: 8 Thread3: 9 Thread3: 10 Press any key to continue... |
Thread1: 1 Thread3: 1 Thread3: 2 Thread3: 3 Thread3: 4 Thread3: 5 Thread3: 6 Thread3: 7 Thread3: 8 Thread3: 9 Thread2: 1 Thread3: 10 Thread1: 2 Thread2: 2 Thread1: 3 Thread2: 3 Thread1: 4 Thread2: 4 Thread1: 5 Thread2: 5 Thread1: 6 Thread2: 6 Thread1: 7 Thread2: 7 Thread1: 8 Thread1: 9 Thread1: 10 Thread2: 8 Thread2: 9 Thread2: 10 Press any key to continue... |
Declare a class that implements the Runnable interface.
That class
then implements the run method.
An instance of the class can then be allocated, passed as an argument when creating Thread, and started.
Create a class that implements the
Runnable
interface
public class
RunThreads2 { public static void main (String[] args) {
Thread p1 = new Thread(new SomeThread2(1));
p1.start();
Thread p2 = new Thread(new SomeThread2(2));
p2.start();
Thread p3 = new Thread(new SomeThread2(3));
p3.start(); } } //end class RunThreads2 |
class SomeThread2
implements Runnable
{ int myID; SomeThread2(int id) { this.myID = id; } public void run() { int i; for (i = 1; i < 11; i++) System.out.println ("Thread"+myID
+ ": " + i); } } //end class SomeThread2 |
Sample Runs |
|
Thread1: 1 Thread1: 2 Thread1: 3 Thread1: 4 Thread1: 5 Thread3: 1 Thread1: 6 Thread2: 1 Thread1: 7 Thread3: 2 Thread1: 8 Thread2: 2 Thread1: 9 Thread3: 3 Thread1: 10 Thread2: 3 Thread3: 4 Thread2: 4 Thread3: 5 Thread2: 5 Thread3: 6 Thread2: 6 Thread3: 7 Thread2: 7 Thread3: 8 Thread3: 9 Thread3: 10 Thread2: 8 Thread2: 9 Thread2: 10 Press any key to continue... |
Thread1: 1 Thread1: 2 Thread1: 3 Thread1: 4 Thread1: 5 Thread1: 6 Thread1: 7 Thread3: 1 Thread2: 1 Thread1: 8 Thread2: 2 Thread3: 2 Thread2: 3 Thread1: 9 Thread2: 4 Thread3: 3 Thread2: 5 Thread1: 10 Thread2: 6 Thread3: 4 Thread2: 7 Thread3: 5 Thread2: 8 Thread3: 6 Thread2: 9 Thread2: 10 Thread3: 7 Thread3: 8 Thread3: 9 Thread3: 10 Press any key to continue... |
Thread-safe Programming
Ř
When two threads independently access and update the same data
object, such as a counter, as part of their code, the updating needs to be
synchronized.
Ř
Because the threads are executed concurrently, it is possible
for one of the updates to be overwritten by the other due to the sequencing
of the two sets of machine instructions executed in behalf of the two
threads.
Ř
To protect against the possibility, a synchronized method can be used to
provide mutual exclusion.
Race Condition
Synchronization - the act of serializing access to
critical sections of code, at various moments during their executions
·
Sun's
Java virtual machine specification states that synchronization is based on monitors
Ř
A
monitor is a concurrency construct that
encapsulates data and functionality for allocating and releasing shared
resources (such as network connections, memory buffers, printers, and so on).
Ř
To
accomplish resource allocation or release, a thread calls a monitor entry
(a special function or procedure that serves as an entry point into a monitor).
§
If
there is no other thread executing code within the monitor, the calling thread
is allowed to enter the monitor and execute the monitor entry's code.
§
But if a thread is already inside of
the monitor, the monitor makes the calling thread wait outside of the monitor
until the other thread leaves the monitor.
·
The
monitor then allows the waiting thread to enter.
§
Because
synchronization is guaranteed, problems such as data being lost or scrambled
are avoided.
·
To
learn more about monitors, study Hoare's landmark paper, ""Monitors: An Operating System
Structuring Concept," first published by the Communications of the
Association for Computing Machinery Inc. in 1974.
The JVM
specification goes on to state that monitor behavior can be explained in terms
of locks.
·
A
lock is a token that a thread must
acquire before a monitor allows that thread to execute inside of a monitor
entry.
o
That
token is automatically released when the thread exits the monitor, to give
another thread an opportunity to get the token and enter the monitor.
·
Java
associates locks with objects: each object is assigned its own lock, and each
lock is assigned to one object.
o
A
thread acquires an object's lock prior to entering the lock-controlled monitor
entry, which Java represents at the source code level as either a synchronized
method or a synchronized statement.
·
The
Java programming language provides two basic synchronization idioms: synchronized
methods and synchronized statements.
o
The
more complex of the two is synchronized statements
·
To
make a method synchronized, simply add the synchronized keyword to its declaration:
public class SynchronizedCounter { private int c = 0; public synchronized void increment() { c++; } public synchronized void decrement() { c--; } public synchronized int value() { return c; } } |
·
Making these methods synchronized has two effects:
o
First, it is not possible for two invocations of synchronized
methods on the same object to interleave.
§
When one thread is executing a synchronized method for an
object, all other threads that invoke synchronized methods for the same object
block (suspend execution) until the first thread is done with the object.
o
Second, when a synchronized method exits, it automatically
establishes a happens-before relationship with any
subsequent invocation of a synchronized method for the same object.
§
This guarantees that changes to the state of the object are
visible to all threads.
·
Synchronized
methods enable a simple strategy for preventing thread interference and memory
consistency errors: if an object is visible to more than one thread, all reads
or writes to that object's variables are done through synchronized methods.
Synchronized method in a thread
class SomeThread3 implements
Runnable { static int
count=0; SomeThread3() { super(); } public void run() { update(); } static public synchronized void update(
){ int myCount = count; int second = (int)(Math.random( ) * 500); try { Thread.sleep(second); } catch (InterruptedException e) { } myCount++; count = myCount; System.out.println("count="+count+
"; thread count=" + Thread.activeCount(
)); } } //end class SomeThread3 |
// import SomeThread3; public class RunThreads3 {
public static void main (String[] args) { int originalThreadCount = Thread.activeCount( );
for (int i=0; i<10; i++) { Thread p = new Thread(new SomeThread3()); p.start(); System.out.println("thread
count=" + Thread.activeCount( ));
}
while (Thread.activeCount() > originalThreadCount ){
// loop until all child threads have exited.
} System.out.println("finally, Count = " +
SomeThread3.count); } }//end class RunThreads3 |
Sample
Run |
thread count=2 thread count=3 thread count=4 thread count=5 thread count=6 thread count=7 thread count=8 thread count=9 thread count=10 thread count=11 count=1; thread count=11 count=2; thread count=10 count=3; thread count=9 count=4; thread count=8 count=5; thread count=7 count=6; thread count=6 count=7; thread count=5 count=8; thread count=4 count=9; thread count=3 count=10; thread count=2 finally, Count = 10 Press any key to continue... |
Issues
Liveness
A
concurrent application's ability to execute in a timely manner is known as its liveness.
Deadlock
v Deadlock describes a
situation where two or more threads are blocked forever, waiting for each
other.
v Example.
·
Alphonse and Gaston are friends, and great believers in
courtesy.
·
A strict rule of courtesy is that when you bow to a friend, you
must remain bowed until your friend has a chance to return the bow.
·
Unfortunately, this rule does not account for the possibility
that two friends might bow to each other at the same time.
·
This example application, Deadlock, models this possibility:
public class Deadlock { static class
Friend { private final
String name; public
Friend(String name) { this.name = name; } public String
getName() { return this.name; } public
synchronized void bow(Friend bower) { System.out.format("%s: %s has bowed to me!%n", this.name, bower.getName()); bower.bowBack(this); } public
synchronized void bowBack(Friend bower) { System.out.format("%s:
%s has bowed back to me!%n", this.name, bower.getName()); } } public static
void main(String[] args) { final Friend alphonse = new Friend("Alphonse"); final Friend gaston = new Friend("Gaston"); new
Thread(new Runnable() { public
void run() { alphonse.bow(gaston);
} }).start(); new
Thread(new Runnable() { public
void run() { gaston.bow(alphonse);
} }).start(); } } |
·
When Deadlock runs, it's extremely likely that both threads
will block when they attempt to invoke bowBack.
·
Neither block will ever end, because each thread is waiting
for the other to exit bow.
·
A
thread is unable to gain regular access to shared resources and is unable to
make progress.
·
Happens
when shared resources are made unavailable for long periods by
"greedy" threads.
·
A
thread often acts in response to the action of another thread.
·
If
the other thread's action is also a response to the action of another thread,
then livelock may result.
·
As
with deadlock, livelocked threads are unable to make
further progress.
·
However,
the threads are not blocked — they are simply too busy responding to each other
to resume work.
Waiting and Notification
·
When
coupled with Java's waiting and notification mechanism, synchronized methods
and/or synchronized statements allow threads to actively communicate, to
cooperate on common goals.
·
Waiting
and notification mechanism supports communication between threads, as follows:
o
A
thread voluntarily waits until some condition (a prerequisite for continued
execution) occurs.
o
At
that time, another thread notifies the waiting thread, to continue its
execution.
·
That
communication is made possible by five methods implemented in the Object class:
Guarded Blocks
·
Threads often have to coordinate their actions.
·
Most common coordination idiom is the guarded block.
·
Such
a block begins by polling a condition that must be true before the block can
proceed.
Approach…
·
Invoke
Object.wait() to suspend the current thread.
·
The
invocation of wait does not return until another thread has issued a notification
that some special event may have occurred — though not necessarily the event
this thread is waiting for:
·
When
wait() is
invoked, the thread releases the lock and suspends execution.
·
At
some future time, another thread will acquire the same lock and invoke Object.notifyAll(), informing all threads waiting on
that lock that something important has happened.
·
Some
time after the second thread has released the lock, the first thread reacquires
the lock and resumes by returning from the invocation of wait.
Example: Producer-Consumer Application
·
This
kind of application shares data between two threads: the producer,
that creates the data, and the consumer, that does something with
it.
·
The
two threads communicate using a shared object.
·
Coordination
is essential: the consumer thread must not attempt to retrieve the data before
the producer thread has delivered it, and the producer thread must not attempt
to deliver new data if the consumer hasn't retrieved the old data.
Data is a
series of text messages, which are shared through an object of type Drop:
public class Drop { //Message
sent from producer to consumer. private
String message; //True if
consumer should wait for producer to send message, false //if
producer should wait for consumer to retrieve message. private boolean empty = true; public
synchronized String take() { //Wait
until message is available. while
(empty) {
try {
wait(); }
catch (InterruptedException e) {} }
//Toggle status. empty
= true;
//Notify producer that status has changed. notifyAll(); return
message; } public
synchronized void put(String message) { //Wait
until message has been retrieved. while
(!empty) {
try { wait(); }
catch (InterruptedException e) {} }
//Toggle status. empty
= false;
//Store message. this.message = message;
//Notify consumer that status has changed. notifyAll(); } } |
·
The
producer thread, defined in Producer, sends a series of familiar messages.
o
The
string "DONE" indicates that all messages have been sent.
o
To
simulate the unpredictable nature of real-world applications, the producer
thread pauses for random intervals between messages.
import java.util.Random; public class Producer implements Runnable {
private Drop drop;
public Producer(Drop drop) {
this.drop = drop; }
public void run() {
String importantInfo[] = {
"Mares eat oats",
"Does eat oats",
"Little lambs eat ivy",
"A kid will eat ivy too"
};
Random random = new Random();
for (int i = 0; i < importantInfo.length; i++) {
drop.put(importantInfo[i]);
System.out.format("MESSAGE SENT: %s%n", importantInfo[i]);
try { Thread.sleep(random.nextInt(5000));
} catch (InterruptedException e) {}
}
drop.put("DONE"); } } |
The
consumer thread, defined in Consumer, simply retrieves the messages and prints them out, until it retrieves
the "DONE" string.
·
This
thread also pauses for random intervals.
import java.util.Random; public class Consumer implements Runnable {
private Drop drop;
public Consumer(Drop drop) {
this.drop = drop; }
public void run() {
Random random = new Random();
for (String message = drop.take(); ! message.equals("DONE"); message = drop.take())
{
System.out.format("MESSAGE RECEIVED: %s%n", message);
try { Thread.sleep(random.nextInt(5000));
} catch (InterruptedException e) {}
} } } |
Finally, the main thread,
defined in ProducerConsumerExample,
that launches the producer and consumer threads
public class ProducerConsumerExample
{
public static void main(String[] args) {
Drop drop = new Drop();
(new Thread(new Producer(drop))).start();
(new Thread(new Consumer(drop))).start(); } } |
Threads in Applets
Example: Two Digital Clocks
//Sample Digital Clock Program showing two
threads import java.awt.*; import java.util.Calendar;
import java.applet.*; public class DigitalClock
extends Applet implements Runnable{ Thread clockThread1 = null, clockThread2 =
null; Font
font = new Font("Courier", Font.BOLD, 48); Color color = Color.green; public void start(){ if
(clockThread1 == null){
clockThread1 = new Thread(this);
clockThread1.start(); } if
(clockThread2 == null){
clockThread2 = new Thread(this);
clockThread2.start(); } } public void stop(){
clockThread1 = null;
clockThread2 = null; } public void run(){
while(Thread.currentThread() ==
clockThread1){
color = Color.green;
repaint();
try{ Thread.currentThread().sleep(1000); }
catch (InterruptedException e){} }
while(Thread.currentThread() == clockThread2){
color = Color.blue;
repaint();
try{ Thread.currentThread().sleep(1500); }
catch (InterruptedException e){} } } public void paint(Graphics g){
Calendar calendar = Calendar.getInstance();
int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute =calendar.get(Calendar.MINUTE); int second =calendar.get(Calendar.SECOND); g.setFont(font); g.setColor(color); g.drawString(hour + ":"+minute/10+minute%10+ ":"+second/10+second%10,
10,60); } } |
High Level
Concurrency Objects
High-level concurrency
features introduced with version 5.0 of the Java platform
See http://java.sun.com/docs/books/tutorial/essential/concurrency/highlevel.html