JAVA THREADS


Contents


Overview

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.

Simple Example

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:

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");
    }
}

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:

	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; }
	}


TEST YOURSELF #1

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.

solution


Synchronization

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.

Synchronized Methods

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.)

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;
    }

}

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.

Synchronized Statements

Sometimes synchronized methods are not sufficient to prevent two threads from interfering with each other. For example, an ATM thread that is asked to perform a withdrawal from a given bank account might first use the Balance method to determine whether the account has enough money, and then use the Withdrawal method to perform the withdrawal. If two ATM threads are asked to withdraw money from the same account, the following sequence of events might take place:

  1. ATM 1 calls Balance, which returns $1,000
  2. ATM 2 calls Balance, which returns $1,000
  3. ATM 1 calls Withdraw, removing $600 from the account.
  4. ATM 2 calls Withdraw, removing an additional $500 from the account.
Since in both cases, the amount returned by the Balance method was more than the requested withdrawal amount, both ATM threads went ahead with their withdrawals. The fact that the Balance and Withdrawal methods were synchronized did not prevent the account from being overdrawn. The problem is that the third event (ATM 1 withdraws $600) invalidated the result of the second event (ATM 2 learns that the current balance is $1,000).

This kind of problem can be prevented by locking an object for the duration of a sequence of statements. The syntax is as follows:

For example, the following code might be used by an ATM thread to withdraw D dollars from a BankAccount B:
synchronized ( B ) {
    if ( D > B.Balance() ) {
        ReportInsuffucientFunds();
    }
    else {
        B.Withdraw( D );
    }
}
While the ATM thread is executing these synchronized statements, no other thread can call any synchronized non-static method of object B, nor can another thread execute synchronized statements that use object B as the object-to-be-locked. Let us reconsider the example above in which two ATM threads are both requested to withdraw money from the same account, now assuming that the synchronized statement list given above is used. In this case, the following sequence of events might take place:
  1. ATM 1 starts its synchronized list of statements (now ATM 2 cannot start its synchronized list of statements until ATM 1 has finished executing the whole list).
  2. ATM 1 calls Balance, which returns $1,000.
  3. ATM 1 calls Withdraw, removing $600 from the account.
  4. ATM 1 is finished with its synchronized list of statements.
  5. ATM 2 starts its synchronized list of statements.
  6. ATM 2 calls Balance, which returns $400.
  7. The current balance is less than the requested withdrawal amount, so ATM 2 calls its ReportInsufficientFunds method.
  8. ATM 2 is finished with its synchronized list of statements.


TEST YOURSELF #2

Consider the following two class outlines, with line numbers included for reference:

1.  public class Synch {
2.
3.     public static void FirstStaticFn() { ... }
4.     public synchronized static void SecondStaticFn() { ... }
5.     public synchronized static void ThirdStaticFn() { ... }
6.
7.     public void FirstFn() { ... }
8.     public void SecondFn() { ... }
9.     public synchronized void ThirdFn() { ... }
10.    public synchronized void FourthFn() { ... }
11. }
12.
13. public class T extends Thread {
14.    private Synch s;
15.
16.    // constructor
17.    public T( Synch syn ) {
18.        s = syn;
19.    }
20.
21.    public void run() {
22.        Synch.FirstStaticFn();
23.        Synch.SecondStaticFn();
24.        Synch.ThirdStaticFn();
25.
26.        s.FirstFn();
27.        s.SecondFn();
28.        s.ThirdFn();
29.
30.        synchronized (s) {
40.            s.FirstFn();
41.        }
42.    }
43. }
Assume that one Synch object S is created, and is passed to the constructor functions of two T objects, whose start methods are then called. Fill in the following table to show which statements in the two T threads might execute simultaneously.

STATEMENT EXECUTED BY FIRST T THREAD STATEMENTS THAT MIGHT BE EXECUTED SIMULTANEOUSLY BY SECOND T THREAD
22
23
24
26
27
28
40

solution


Communicating Among Threads

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 wait method

The way to call the wait method is as follows:

while (! <condition> ) {
    wait();
}
< code that needed the condition to be true >
Note:
  1. The object that calls wait must be locked by the current thread. (This means that the method that contains the call to wait must be synchronized, or must have been called from a synchronized method, or from a synchronized statement list in one of the current thread's methods.) If the current thread does not hold a lock on the object that calls wait, an IllegalMonitorStateException will be thrown.

  2. It is important to put the test for the condition inside a loop, not just inside an if. Just because the waiting thread is reactivated (by another thread calling notify or notifyAll) does not guarantee that the condition has been satified.

  3. When wait is called, the current thread's lock on the object that calls wait is released. When the thread is reactivated (due to another thread calling notify or notifyAll) the lock is reacquired. This means that if another thread has acquired the lock while the first thread was waiting, the first thread may not begin executing right away when its wait is ended by a call to notify or notifyAll.

  4. The wait method can throw an InterruptedException, so a call to wait must either be inside a try block that catches that exception, or the enclosing method must include InterruptedException in its throws clause.

  5. The wait method can also be called with a time (in milliseconds, with an optional second parameter to specify additional nanoseconds). In this case, the wait ends either when the thread is notified, or when the given time has elapsed, whichever comes first. For example:
    	wait(100);    // wait at most 100 milliseconds
    	wait(100, 5)  // wait at most 100 milliseconds + 5 nonoseconds
    	

The notify and notifyAll methods

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.


TEST YOURSELF #3

Write a Queue class with the following 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

Write two classes named SetVals and GetVals, both of which extend Thread, and both of which work on the same Queue object: