Chapter 1 Multithreading
Chapter 1 Multithreading
CHAPTER ONE
MULTITHREADING
Processes
A process has a self-contained execution environment.
A process generally has a complete, private set of basic run-time
resources; in particular, each process has its own memory space.
Processes are often seen as synonymous with programs or
applications.
However, what the user sees as a single application may in fact be a
set of cooperating processes.
To facilitate communication between processes, most operating
systems support Inter Process Communication (IPC) resources, such as
pipes and sockets.
IPC is used not just for communication between processes on the same
system, but processes on different systems.
1. Threads and Processes…
6
Threads
Threads are sometimes called lightweight processes.
Both processes and threads provide an execution environment, but creating
a new thread requires fewer resources than creating a new process.
Threads exist within a process — every process has at least one thread.
Threads share the process's resources, including memory and open files.
This makes for efficient, but potentially problematic, communication.
Multithreaded execution is an essential feature of the Java platform.
Every application has at least one thread — or several, if you count system
threads that do things like memory management and signal handling.
But from the application programmer's point of view, you start with just one
thread, called the main thread.
This thread has the ability to create additional threads, as we'll demonstrate
in the next section.
2. The Main Thread
7
When a Java program starts up, one thread begins running immediately.
This is called the main thread of your program, because it is the one that is
executed when your program begins.
The main thread is important for two reasons:
It is the thread from which other “child” threads will be spawned.
Often it must be the last thread to finish execution because it performs
various shutdown actions.
Although the main thread is created automatically when your program is
started, it can be controlled through a Thread object.
To do so, you must obtain a reference to it by calling the method
currentThread() of Thread class:
static Thread currentThread( )
This method returns a reference to the thread in which it is called.
Once you have a reference to the main thread, you can control it just like
any other thread.
public static void main(String args[]) {
Thread t = Thread.currentThread();
System.out.println("Current thread: " + t);
8 //change the name of the thread
t.setName("Main Thread");
System.out.println("After name change: " + t);
try {
for (int n = 5; n > 0; n--) { • When thread t is converted to string,
System.out.println(n); it returns a thread name, its priority
Thread.sleep(1000); & thread group name.
} Output:
} catch (InterruptedException e) { Current thread: Thread[main,5,main]
After name change: Thread[Main
System.out.println("Main thread
Thread,5,main]
interrupted");
5
} 4
} 3
2
1
3. Creating a Thread
9
A. Thread Class
The Thread class lets you create an object that can be run as a thread in a
multi-threaded Java application.
One way to create a thread is to create a class that extends Thread class.
The extending class must override the run() method, which is the entry point
for the new thread.
It must also call start() to begin execution of the new thread.
Commonly used constructors of Thread class:
Thread()
Thread(String name)
Thread(Runnable r)
Thread(Runnable r, String name)
Thread(ThreadGroup group, Runnable r)
Thread(ThreadGroup group, Runnable r, String name)
3. Creating a Thread…
11
The Thread class defines several methods that help manage threads.
Method Meaning
static int activeCount() Returns the number of active threads.
Fills the specified array with a copy of each active thread. The
static int enumerate(Thread[] t)
return value is the number of threads added to the array.
String getName() Returns the name of the thread.
int getPriority() Returns the thread’s priority.
void interrupt() Interrupts this thread.
boolean interrupted() Checks to see if the thread has been interrupted.
void setPriority(int priority) Sets the thread’s priority.
void setName(String name) Sets the thread’s name.
static void sleep(int Causes the currently executing thread to sleep for the specified
milliseconds) number of milliseconds.
This method is called when the thread is started. Place the code
void run()
that you want the thread to execute inside this method.
void start() Starts the thread.
This causes the current thread to move from the running state to
static void yield()
the ready state, so that other threads may get a chance to run.
3. Creating a Thread…
12
B. Runnable Interface
The easiest way to create a thread is to create a class that
implements the Runnable interface.
Runnable abstracts a unit of executable code.
The run() method can call other methods, use other classes, and
declare variables, just like the main thread can.
The only difference is that run() establishes the entry point for
another, concurrent thread of execution within your program.
This thread will end when run() returns.
3. Creating a Thread…
16
To run it, you will instantiate an object of type Thread passing the object of
the class that implements the Runnable interface as parameter.
Thread defines several constructors for this.
Thread(Runnable threadObj)
Thread(Runnable threadOb, String threadName)
In the constructor, threadObj is an instance of a class that implements the
Runnable interface.
This defines where execution of the thread will begin.
The name of the new thread is specified by threadName.
After the new thread is created, it will not start running until you call its
start() method, which is declared within Thread.
In essence, start() executes a call to run().
void start( )
Example: creating thread using Runnable
class TestThread implements Runnable {
public void run() {
17 try {
for(int i = 5; i > 0; i--) {
System.out.println("Child Thread: " + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Child interrupted.");
}
System.out.println("Exiting child thread.");
}
}
class ThreadDemo {
public static void main(String args[]) {
Thread tt = new Thread(new TestThread());
tt.start();
Creating thread using Runnable …
18
However, these sleep times are not guaranteed to be precise, because they
are limited by the facilities provided by the underlying OS.
Also, the sleep period can be terminated by interrupts.
So, you cannot assume that invoking sleep will suspend the thread for
precisely the time period specified.
Example: main thread uses sleep to print messages at four-second intervals:
public class SleepMessage extends Thread{
public void run(){
21
String info[] = {"Mares eat oats", "Dogs eat oats", "Little lambs eat ivy",
"A kid will eat ivy too"};
for (int i = 0; i < info.length; i++) {
try {
Thread.sleep(4000); //sleep 4 seconds
System.out.println(info[i]);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
public static void main(String args[]) throws InterruptedException {
SleepMessage sm = new SleepMessage();
sm.start();
}
}
4. Controlling Thread…
22
II. Interrupts
An interrupt is an indication to a thread that it should stop what it is doing and
do something else.
It's up to the programmer to decide exactly how a thread responds to an
interrupt, but it is very common for the thread to terminate.
A thread sends an interrupt by invoking interrupt() on the Thread object for the
thread to be interrupted.
For the interrupt mechanism to work correctly, the interrupted thread must
support its own interruption.
How to support interrupt depends on what it's currently doing.
If the thread is frequently invoking methods that throw InterruptedException, it
simply returns from the run method after it catches that exception.
4. Controlling Thread…
23
III. Joins
It is not uncommon for one thread to need the result of another thread.
For example, a web browser loading an HTML page in one thread might create
a second thread to retrieve every image embedded in the page.
Java provides three join() methods to allow one thread to wait for another
thread to finish before continuing. These are:
void join() throws InterruptedException
void join(long milliseconds) throws InterruptedException
void join(long milliseconds, int nanoseconds) throws InterruptedException
The first variant waits indefinitely for the joined thread to finish.
The second two variants wait for the specified amount of time, after which they
continue even if the joined thread has not finished.
As with the sleep() method, nanosecond accuracy is not guaranteed.
4. Controlling Thread…
26
The joining thread (i.e., the one that executes the join() method) waits for the
joined thread (i.e, the one whose join() method is invoked) to finish.
The thread on which the join() is executed is said to have joined the thread
whose name is used to call the join() method.
The join() method allows one thread to wait for the completion of another.
The join() method waits for a thread to die.
In other words, it causes the currently running thread to stop executing until the
thread it joins with completes its task.
4. Controlling Thread…
27
C. notifyAll()
It wakes up all the threads that called wait() on the same object.
The highest priority thread will run first in most of the situation, though not
guaranteed.
Other things are same as notify() method above.
synchronized(lockObject) {
establish_the_condition;
lockObject.notifyAll();
}
class Customer { class Test {
int amount = 10000; public static void main(String args[]) {
synchronized void withdraw(int amount) { final Customer c = new Customer();
System.out.println("going to withdraw..."); Thread t1 = new Thread() {
32
if (this.amount < amount) { public void run() {
System.out.println("Less balance, c.withdraw(15000);
waiting for deposit"); }
try { };
wait(); t1.start();
} catch (Exception e) {} Thread t2 = new Thread() {
} public void run() {
this.amount -= amount; c.deposit(10000);
System.out.println("withdraw completed..."); }
} };
synchronized void deposit(int amount) { t2.start();
System.out.println("going to deposit..."); }
this.amount += amount; }
System.out.println("deposit completed... ");
notify();
}
5. Thread States: Life Cycle of a Thread
33
A thread can enter the Blocked state (i.e., become inactive) for several
reasons.
It may have invoked the join(), sleep(), or wait() method, or some other
thread may have invoked these methods.
It may be waiting for an I/O operation to finish.
A blocked thread may be reactivated when the action inactivating it is
reversed.
For example, if a thread has been put to sleep and the sleep time has
expired, the thread is reactivated and enters the Ready state.
Finally, a thread is finished if it completes the execution of its run() method.
5. Thread States: Life Cycle…
36
When a running thread calls wait(), the thread enters a waiting state for the
particular object on which wait() was called.
One thread in the waiting state for a particular object becomes ready on a
call to notify() issued by another thread associated with that object.
Every thread in the waiting state for a given object becomes ready on a call
to notifyAll() by another thread associated with that object.
A thread enters the dead state when its run() method either completes or
throws an uncaught exception.
The isAlive() method is used to find out the state of a thread.
It returns true if a thread is in the ready, blocked, or running state; it returns
false if a thread is new and has not started or if it is finished.
6. Synchronization
37
39
machine.
A single increment expression c++ can be decomposed into three steps:
invokes decrement.
If the initial value of c is 0, they might interleave as follows:
1. Thread A: Retrieve c.
2. Thread B: Retrieve c.
3. Thread A: Increment retrieved value; result is 1.
4. Thread B: Decrement retrieved value; result is -1.
5. Thread A: Store result in c; c is now 1.
6. Thread B: Store result in c; c is now -1.
Thread A's result is lost, overwritten by Thread B.
6. Synchronization…
40
Example: if you run this program, you will get unexpected output
because of thread interference and memory consistency errors
class ThreadError extends Thread { public static void main(String[] args)
static int count = 0; throws InterruptedException {
public void run() { ThreadError t1 = new
ThreadError();
for (int x = 0; x < 10000;
x++) { ThreadError t2 = new
ThreadError();
count++;
t1.start();
count--;
t2.start();
}
}
System.out.println(this.getName() + "
count: " + count); }
}
6. Synchronization…
43
synchronized statements.
6. Synchronization…
44
A. Synchronized Methods
To avoid thread interference and memory consistency errors, it is necessary
to prevent more than one thread from simultaneously entering a certain part
of the program, known as the critical region.
You can use the keyword synchronized to synchronize the method so that
only one thread can access the method at a time.
Synchronized methods is a simple strategy for preventing thread
interference and memory consistency errors.
Synchronization is easy in Java, because all objects have their own implicit
object monitor associated with them.
To enter an object’s monitor, just call a method that has been modified with
the synchronized keyword.
While a thread is inside a synchronized method, all other threads that try to
call it on the same instance have to wait.
To exit the monitor & relinquish control of the object to another waiting
thread, the owner of monitor simply returns from the synchronized method.
To make a method synchronized, simply add the synchronized keyword to its
declaration:
public class Counter {
private int c = 0;
45 public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
}
If count is an instance of Counter, then making these methods synchronized
has two effects:
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 blocks until the
first thread is done with the object.
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.
Example: using synchronized methods
46
B. Synchronized Statements
Another way to create synchronized code is with synchronized
statements.
Synchronized statements enable you to synchronize part of the code
in a method instead of the entire method.
This increases concurrency.
synchronized(object) {
// statements to be synchronized
}
Here, object is a reference to the object being synchronized.
member of object occurs only after the current thread has successfully
entered object’s monitor.
6. Synchronization…
50
Synchronized statements are also useful for improving concurrency with fine-
grained synchronization.
Suppose, for example, class MsCounter has two instance fields, c1 and c2,
that are never used together.
All updates of these fields must be synchronized, but there's no reason to
prevent an update of c1 from being interleaved with an update of c2 —
and doing so reduces concurrency by creating unnecessary blocking.
Instead of using synchronized methods or otherwise using the lock associated
with this, we create two objects solely to provide locks.
6. Synchronization…
52
You must be absolutely sure that it really is safe to interleave access of the affected
fields.
7. Deadlock
53
A special type of error that you need to avoid that relates specifically to
multitasking is deadlock.
Deadlock occurs when two threads have a circular dependency on a pair of
synchronized objects.
Deadlock describes a situation where two or more threads are blocked
forever, waiting for each other.
For example, suppose one thread enters the monitor on object X and
another thread enters the monitor on object Y.
If the thread in X tries to call any synchronized method on Y, it will block as
expected.
However, if the thread in Y, in turn, tries to call any synchronized method on
X, the thread waits forever, because to access X, it would have to release its
own lock on Y so that the first thread could complete.
Deadlock is a difficult error to debug for two reasons:
In general, it occurs only rarely, when the two threads time-slice in just the
right way.
It may involve more than two threads and two synchronized objects.
7. Deadlock
54
Producer/Consumer Problem
The producer-consumer problem (also known as the bounded-buffer
problem) is another classical example of a multithread synchronization
problem.
The problem describes two threads, the producer and the consumer, who
share a common, fixed-size buffer.
The producer’s job is to generate a piece of data and put it into the buffer.
The consumer is consuming the data from the same buffer simultaneously.
The problem is to make sure that the producer will not try to add data into
the buffer if it is full and that the consumer will not try to remove data from
an empty buffer.
The solution for this problem involves two parts.
The producer should wait when it tries to put the newly created product into
the buffer until there is at least one free slot in the buffer.
The consumer, on the other hand, should stop consuming if the buffer is
empty.
7. Deadlock…
57
When a task adds an int to the buffer, if the buffer is full, the task will wait
for the notFull condition.
When a task deletes an int from the buffer, if the buffer is empty, the task
will wait for the notEmpty condition.
public class ProducerConsumerTest {
public static void main(String[] args) {
CubbyHole c = new CubbyHole();
Producer p1 = new Producer(c, 1);
Consumer c1 = new Consumer(c, 1);
p1.start();
58 c1.start();
}
}
class CubbyHole {
private int contents;
private boolean available = false;
public synchronized int get() {
while (available == false) {
try {
wait();
} catch (InterruptedException e) {}
}
available = false;
notifyAll();
return contents;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait();
} catch (InterruptedException e) { }
}
contents = value;
available = true;
notifyAll();
}
}
class Consumer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Consumer(CubbyHole c, int num) {
cubbyhole = c;
number = num;
59 }
public void run() {
int value = 0;
for (int i = 0; i < 10; i++) {
value = cubbyhole.get();
System.out.println("Consumer #" + this.number + " got: " + value);
}
}
}
class Producer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
cubbyhole.put(i);
System.out.println("Producer #" + this.number + " put: " + i);
try {
Thread.sleep((int)(Math.random() * 100));
} catch (InterruptedException e) { }
}
}
}
8. Event Dispatching Thread
60
try {
Thread.sleep(3000);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
label.setText("New text!");
}
});
} catch (InterruptedException ix) {
System.out.println("interrupted while waiting on invokeAndWait()");
} catch (InvocationTargetException x) {
System.out.println("exception thrown from run()");
}
}
}
8. Event Dispatching Thread…
64