So far, all of the code we've written has been sequential: when a program runs, one statement after another is executed, and when a method is called, the caller waits until the method returns before going on to the next statement. Another approach is to let multiple methods be active simultaneously. In Java, this approach involves the use of threads.
Threads can be implemented by extending the Thread class, which is defined in java.lang. (A second way to implement threads - by implementing the Runnable interface - is discussed below.) A Thread object is run by calling its start method. The start method spawns a new thread of control, and then returns (without waiting for the new thread to finish). Therefore, it is possible to have many different threads running at the same time, by calling each thread's start method in turn.
When a new thread is started, its run method is called (by the Java virtual machine, not by your program). The thread exists until its run method returns.
Mechanisms are provided to prevent threads from interfering with
each other (e.g., trying to read and write the same data), and to allow
communication among threads.
Here is a simple example Java program that creates two threads;
one thread computes and prints the sums of even numbers, and the
other computes and prints the sums of odd numbers:
This example code creates two threads (s1 and s2), and then calls their
start methods;
this in turn causes the threads' run methods to be invoked.
The main program then goes on to print "in main" 100 times.
Conceptually, the two printSum threads, as well as the main thread,
run simultaneously.
However, if the program is run on a single-processor machine,
only one thread will actually be running at any one time.
It is up to the JVM to schedule multiple threads (you can influence
this by
setting their priorities).
It may be that the first thread gets such a large block of time that
when you run the example program you will get most or even all of the
output from the first thread before you see any output from the second
thread.
You might be able to get some interleaving by increasing the second
argument to the first thread's constructor function.
Another way to get interleaving is to cause the threads to "sleep"
periodically, using the
sleep method of the Thread class:
Define a class called PrintWord that extends the Thread class.
The PrintWord constructor should take two arguments: a string to be
printed, and the number of microseconds to sleep after printing the
string.
The PrintWord class's run method should print the appropriate string
followed by a space, and then should sleep for the appropriate time.
If the sleep is interrupted, the run method should return.
Write a program that creates two PrintWord threads, using different
strings and different sleep times, then calls their start methods.
Some real-world tasks are better modeled by a program that uses threads
than by a normal, sequential program.
For example, consider a bank whose accounts can be accessed and updated
by any of a number of automatic teller machines (ATMs).
Each ATM could be a separate thread, responding to deposit and
withdrawal requests from different users simultaneously.
Of course, it would be important to make sure that two users did not
access the same account simultaneously.
This is done in Java using synchronization, which can be
applied to individual methods, or to sequences of statements.
One or more methods of a class can be declared to be synchronized.
When a thread calls an object's synchronized method, the whole object is
locked.
This means that if another thread tries to call any synchronized
method of the same object, the call will block until the lock is released
(which happens when the original call finishes).
In general, if the value of a field of an object can be changed, then
all methods that read or write that field should be synchronized to
prevent two threads from trying to write the field at the
same time, and to prevent one thread from reading the field while
another thread is in the process of writing it.
Here is an example of a BankAccount class that uses synchronized methods
to ensure that deposits and withdrawals cannot be performed simultaneously,
and to ensure that the account balance cannot be read while either a
deposit or a withdrawal is in progress.
(To keep the example simple, no check is done to ensure that a withdrawal
does not lead to a negative balance.)
Note that the BankAccount's constructor is not declared to be synchronized.
That is because it can only be executed when the object is being created, and
no other method can be called until that creation is finished.
Static methods can also be synchronized.
In that case, another thread cannot simultaneously call any other
synchronized static method;
it can however call synchronized non-static methods of any object of
that class.
Simple Example
class printSum extends Thread {
// run method prints, for each k in the range start..end, the sum
// of the even numbers from 1 to k if "even" is true; otherwise,
// it prints the sum of the odd numbers from 1 to k
// private data members
int start, end;
boolean even; // true means print sum of even numbers,
// false means print sum of odd numbers
// constructor: set start, end, and even
public printSum(int s, int e, boolean ev) {
start = s;
end = e;
even = ev;
}
// run method; print sums of even or odd numbers depending on
// the value of "even"
public void run() {
for (int k=start; k<=end; k++) {
int sum = 0;
for (int j=1; j<= k; j++) {
if (even && j/2 * 2 == j) sum += j;
else if (!even && j/2 * 2 != j) sum += j;
}
if (even) System.out.println("even sum: " + sum);
else System.out.println("odd sum: " + sum);
}
}
}
public class threadDemo1 {
public static void main(String[] args) {
printSum s1 = new printSum(1, 100, true);
printSum s2 = new printSum(1, 100, false);
s1.start();
s2.start();
for (int k=1; k<=100; k++) System.out.println("in main");
}
}
for (int k=start; k<=end; k++) {
int sum = 0;
for (int j=1; j<= k; j++) {
if (even && j/2 * 2 == j) sum += j;
else if (!even && j/2 * 2 != j) sum += j;
}
if (even) System.out.println("even sum: " + sum);
else System.out.println("odd sum: " + sum);
sleep(100);
}
The call to sleep causes the thread to wait for 100 milliseconds (1/10 of
a second) after each iteration of the loop.
However, this code is not quite correct:
the sleep method can throw an InterruptedException, and a thread's
run method is not allowed to throw any exceptions (because Thread.run,
which is being overridden, throws no exceptions).
Therefore, we must add code to catch that exception:
for (int k=start; k<=end; k++) {
int sum = 0;
for (int j=1; j<= k; j++) {
if (even && j/2 * 2 == j) sum += j;
else if (!even && j/2 * 2 != j) sum += j;
}
if (even) System.out.println("even sum: " + sum);
else System.out.println("odd sum: " + sum);
try {
sleep(100);
} catch (InterruptedException e) { return; }
}
Synchronization
Synchronized Methods
public class BankAccount {
private double balance;
// constructor: set balance to given amount
public BankAccount( double initialDeposit ) {
balance = initialDeposit;
}
public synchronized double Balance( ) {
return balance;
}
public synchronized void Deposit( double deposit ) {
balance += deposit;
}
public synchronized void Withdraw( double withdrawal ) {
balance -= withdrawal;
}
}
STATEMENT EXECUTED BY FIRST T THREAD | STATEMENTS THAT MIGHT BE EXECUTED SIMULTANEOUSLY BY SECOND T THREAD |
22 | |
23 | |
24 | |
26 | |
27 | |
28 | |
40 | |
Java provides three methods that threads can use to communicate with each
other:
wait,
notify, and
notifyAll.
These methods are defined for all Objects (not just Threads).
The idea is that a method called by a thread may need to wait for some
condition to be satisfied by another thread;
in that case, it can call the wait method, which causes its thread to
wait until another thread calls notify or notifyAll.
The way to call the wait method is as follows:
As was the case for calls to wait, the object that calls notify or
notifyAll must be locked by the current thread.
A call to notify causes at most one thread waiting on the same object
to be notified (i.e., the object that calls notify must be the same as
the object that called wait).
A call to notifyAll causes all threads waiting on the same object to
be notified.
If more than one thread is waiting on that object, there is no way to
control which of them is notified by a call to notify (so it is often
better to use notifyAll than notify).
Note that a call to notify or notifyAll that occurs before a call
to wait is not "remembered";
i.e., it has no effect on the waiting thread.
Write a Queue class with the following methods:
Write two classes named SetVals and GetVals, both of which extend Thread,
and both of which work on the same Queue object:
Write a main method that performs the following steps:
Note: When you run your program, you will probably want to redirect the
standard input to be read from a file.
Here is a short summary of some other useful Thread methods:
The Runnable interface has only one method:
There are two ways to create new kinds of threads:
Here is a very simple example just to illustrate how to use the second
approach to creating threads:
Java includes the Thread class and the Runnable interface to permit you
to write multithreaded programs.
In both cases, you need to define a class that includes a run
method, and you need to start the thread by calling its start
method.
While some programming problems are more naturally addressed using multiple
threads, it is important to consider how threads will interact to prevent
unintended interference or deadlock.
Locking of objects (via synchronized methods and statement lists) helps
prevent multiple threads from interfering, and methods are also provided
to allow threads to communicate with each other.
The wait method
while (! <condition> ) {
wait();
}
< code that needed the condition to be true >
Note:
wait(100); // wait at most 100 milliseconds
wait(100, 5) // wait at most 100 milliseconds + 5 nonoseconds
The notify and notifyAll methods
public Queue() // constructor: initialize the queue to be empty
public synchronized void Enqueue(Object ob) // add ob to the end of the queue, then call notifyAll
public synchronized Object Dequeue() // wait until the queue is non-empty
// then remove and return the object from the front of the queue
Other useful methods
return the current priority of the thread
set the thread's priority to the max of the given value and the
highest priority allowed for the thread's group
interrupt the thread's execution
return true iff the thread has been started and has not yet
terminated
wait for the thread to terminate (can also be called with one
argument -- a maximum number of milliseconds to wait)
cause the thread to pause so that other runnable threads have
a chance to run (note: it is up to the thread scheduler to determine
which thread -- including the one whose yield method is called --
actually does run)
The Runnable Interface
public void run();
Thus, every Thread implements the Runnable interface, since every Thread
includes a run method.
An advantage of the second approach is that the new class can be a subclass
of any class, not just of the Thread class.
class myThread implements Runnable {
public void run() {
System.out.println("I'm running!");
}
}
public class tstRunnable {
public static void main(String[] args) {
myThread my1 = new myThread();
myThread my2 = new myThread();
new Thread(my1).start();
new Thread(my2).start();
}
}
Summary