CHAPTER 16

Multi-threading

What is a thread? A thread executes a sequence of steps. The code in a method defines a series of actions. A thread executes the statements in a method sequentially. A thread is a sequence of execution. The statements in a method define a sequence of execution. A thread executes statements in a method one by one. Some of the statements being executed result in invocation of another method or constructor.

Every thread has a task, which it is executing. The task for a thread is defined in a method, which it is supposed to finish. What is required by a thread for executing the task (method)? In Java a thread uses a runtime stack to carry out its task. Initially the runtime stack is loaded with the method (frame is created) which is the target for the thread to finish. We have seen how the runtime stack is used when a constructor is invoked from the main() method in Figures 11.111.4 in Chapter 11.

Multi-threading is about having multiple threads executing simultaneously. Each thread would be utilizing its own separate runtime stack to execute its task. Each thread would be executing its own task independently using its separate runtime stack.

Initially when the JVM starts, we have one of the threads called main. The execution of the application, i.e. main() method starts in this thread. Except for this main thread, whose task is to finish the main() method, all other threads have their task defined using the run() method of a Runnable. Runnable is the name of an interface. Different implementations of the run() method are used to define different tasks for different threads.

16.1 Thread CLASS AND THREAD OF EXECUTION

In Java we have a class called Thread. Creating an instance of Thread is just like creating an instance of any other class. To distinguish the Thread class from the thread which is a sequence of execution (using a runtime stack), we will call the thread which runs in the runtime stack as a thread of execution. So, if we want to have multi-threading, we would require multiple threads of execution. Each thread of execution executes the task in a separate runtime stack. The thread of execution is over as soon as the task (method) has finished, and the runtime stack is also removed. The runtime stacks correspond to threads of execution and not to the instance of Thread. But corresponding to each thread of execution, there is always a single instance of Thread which maintains information about the thread of execution. Also invoking methods on the instance of Thread can effect the execution in the corresponding thread of execution.

16.2 CREATING A NEW THREAD OF EXECUTION

How do we create a new thread of execution? What are the steps for creating a new thread of execution? Firstly the task for the thread of execution should be defined by having a class implement the Runnable interface and having the implementation of the run() method. This run() method will be the task for the new thread of execution which we want to create. Once we have this kind of a class, then the following steps are required to create a new thread of execution:

  1. Create an instance of the class which implements Runnable and has an implementation of the run() method.
  2. Create an instance of Thread class using the Runnable created in Step 1. One of the constructors in the Thread class takes Runnable as a parameter.
  3. Set up properties for the new thread of execution (like priority, etc.) by invoking methods on the Thread instance created in Step 2. This is an optional step, since we may not want to change the initial values of the properties.
  4. Call the start() method on the instance of Thread created in Step 2.

The invocation of start() method in the last step would create the new thread of execution, i.e. it will create the runtime stack, load a run() method in the runtime stack and initialize the program counter to the first statement in the run() method. In the current thread of execution, i.e. the one which called the start() method, it will move to the next statement after the start() method. While in the new thread of execution the execution has just begun, i.e. the current thread of execution does not wait for the run() method which is started in a new runtime stack to finish. When we call start() method, it is the run() method which starts execution, but in a new thread of execution. It is not part of the same thread, i.e. the frame for the run() is not created in the same runtime stack, but it is created in a separate new runtime stack, where it executes independently. So, here it is important to note that simply creating an instance of Thread does not create a new thread of execution. The instance of Thread is distinct from the thread of execution.

So, after a successful completion of the start() method, we will have an additional runtime stack where the execution of run() method is initiated.

On an instance of Thread, the start() method can be invoked only once. To create a new thread of execution, we cannot have more than one thread of execution created from a single instance of Thread class. Corresponding to a thread of execution, which executes in a runtime stack, there is always an instance of Thread.

We can obtain the instance of Thread for the currently running thread of execution by using the static method currentThread() in the Thread class:

    1     public static Thread currentThread()

Inside any method, if we want to access the instance of Thread which is executing the method at runtime, we can use Thread.currentThread().

When we call the start() method, a new thread of execution is created, and it will execute independently. This new thread of execution exists till the run() method is not completed. Once the run() method has finished, then the thread of execution is no longer there. Only the Thread instance remains. We can check whether the thread of execution corresponding to a Thread instance exists or not by calling the isAlive() method:

    1    public boolean isAlive()

The isAlive() method will return false before the start() method and after the execution of run() has finished, otherwise it returns true.

Let us consider the echo server application as given in Listing 15.3. In this EchoServer class we would like to have the main thread wait for a new connection, and whenever a client connects, it could create a new thread of execution which can handle the new connection, while the main thread can again go back to accepting the next connection from another client.

In the EchoServer exercise we can have an implementation of the run() method of Runnable which can handle the connection.

Let us call this class as Echoer. An instance of Echoer will be responsible for handling a single connection from the client independently in a separate thread of execution. So, the handling of a client, which involves reading bytes coming from the client and sending them back till the client disconnects can be implemented in a run() method. The Echoer class may implement the Runnable interface as given in Listing 16.1.

 

Listing 16.1. Echoer class implementing Runnable interface

    1 package com.classofjava.net;
 2
 3 import java.io.*;
 4 import java.net.*;
 5
 6 public class Echoer implements Runnable {
 7     private Socket socket;          // the socket connection to the
           client
 8     private InputStream in;         // the inputstream of the socket
 9     private OutputStream out;       // the outputstream of the socket
 10    private boolean      localEcho;// whether to display the byte
           received locally
 11
 12    public Echoer(Socket s) {
 13        socket = s;
 14        in = socket.getInputStream();
 15        out = socket.getOutputStream();
 16    }
 17
 18    public void setLocalEcho(boolean b) {
 19        localEcho = b;
 20    }
 21
 22    public boolean isLocalEcho() {
 23        return localEcho;
 24    }
 25
 26    public void run() {
 27        try {
 28            int i = in.read();
 29            while (i != -1) {
 30                if (localEcho) { // display conditionally
 31                    System.out.print((char)i);
 32                }
 33                out.write(i);
 34                i = in.read();
 35             }
 36             in.close();
 37             out.close();
 38             socket.close();
 39        }
 40        catch(IOException ioe) {
 41            ioe.printStackTrace();
 42        }
 43    }
 44 }

The EchoServer may now be modified as given in Listing 16.2.

 

Listing 16.2. Multi-threaded version of EchoServer

    1 package com.classofjava.net;
 2
 3 import java.io.*;
 4 import java.net.*;
 5
 6 public class EchoServer {
 7     private ServerSocket ss;
 8
 9     public EchoServer(int portno) throws IOException {
 10        ss = new ServerSocket(portno);
 11    }
 12
 13    public void run() throws IOException {
 14        while (true) {
 15            Socket socket = ss.accept();
 16            Echoer echoer = new Echoer(socket);
 17            echoer.setLocalEcho(true);
 18            Thread thread = new Thread(echoer);
 19            thread.start();
 20        }
 21    }
 22
 23    public static void main(String[] args) throws IOException {
 24        EchoServer es = new EchoServer(Integer.parseInt(args[0]));
 25        es.run();
 26    }
 27 }
16.3 ThreadGroup

The instances of Thread are organized and managed in a hierarchial manner, using the ThreadGroup, i.e. they have a tree structure similar to the directory structure, like we have the directories and subdirectories, each directory contains files and other directories. Similarly, the Thread instances belong to ThreadGroup and a ThreadGroup can have parent ThreadGroup. Every ThreadGroup instance has a name, which cannot be modified once the ThreadGroup instance is created. The ThreadGroup class has two constructors as given in Listing 16.3. A ThreadGroup has a number of child ThreadGroups and Threads. When a thread finishes execution it gets automatically removed from its thread group and is eligible for garbage collection if there is no other reference.

 

Listing 16.3. ThreadGroup constructors

    1     ThreadGroup(String name)
 2     ThreadGroup(ThreadGroup parent, String name)

An instance of ThreadGroup can be created by specifying the name of the thread group and a parent ThreadGroup. If the parent is not specified, then the thread group of the creating thread (current thread) becomes the parent thread group for the new thread group created. The constructor throws a NullPointerException in case the parent is specified as null.

The ThreadGroup class has various properties and methods which affect the execution of the threads belonging to that thread group. Some of the common methods of the ThreadGroup class are given in Listing 16.4.

 

Listing 16.4. ThreadGroup methods

    1     public final String getName()
 2     public final ThreadGroup getParent()
 3     public final int getMaxPriority()
 4     public final void setMaxPriority(int maxPriority)
 5     public final boolean isDaemon()
 6     public final void setDaemon(boolean daemon)
 7     public boolean isDestroyed()
 8     public final void destroy()
 9     public int activeCount()
 10    public int activeGroupCount()
 11    public int enumerate(Thread[] list)
 12    public int enumerate(Thread[] list, boolean recurse)
 13    public int enumerate(ThreadGroup[] list)
 14    public int enumerate(ThreadGroup[] list, boolean recurse)
 15    public void interrupt()
 16    public void list()
 17    public boolean parentOf(ThreadGroup g)

The ThreadGroup class has getName() and getParent() methods to return the name and the parent thread group of a thread group. There is only one system thread group whose parent is null. No other thread group can be created with the null parent.

The ThreadGroup class has a few properties like the maxPriority and daemon. The getMaxPriority() and setMaxPriority() methods are used to set maxPriority of a thread group. The maxPriority setting does not affect the priority settings of any running thread, it only affects the priority setting for the new thread created subsequently and any subsequent invocation of setPriority() on threads belonging to the thread group. It also affects the subsequent invocation of setMaxPriority() on any of its child thread groups. The setPriority() method sets the priority of a thread to the minimum of the priority being set and the current value of the maxPriority setting in its thread group. Similarly, the setMaxPriority() method invocation on a thread group sets the maxPriority setting to the minimum of the maxPriority value being set and the value of the maxPriority of its parent thread group.

A ThreadGroup instance may be destroyed by using the destroy() method. The destroy() method fails in case the ThreadGroup instance is not empty. When a thread group is destroyed, then it is automatically removed from its parent. The daemon setting of a thread group has no relation with the daemon setting of the threads. Setting a thread group to be daemon is like marking the thread group for destruction as soon as it becomes empty. The isDaemon() and setDaemon() methods are used to get and set the daemon settings of a thread group. Once a ThreadGroup is destroyed, then it can neither be used as a parent for creating another thread group nor can it be used to create a new thread.

The ThreadGroup class has a method called activeCount() which would return the number of active threads in the ThreadGroup instance and all its child ThreadGroups. The activeGroupcount() method can be used to find the number of child thread groups which are active (not destroyed). The enumerate() method can be used to get all the active threads or all the active thread groups for a given thread group. The enumerate() method is overloaded, and according to the first parameter, it would update the array of the thread or the thread group. The second boolean parameter is used to specify whether the list is to be populated with threads of the thread groups for the given thread group instance only, or the list should include the list from the child thread groups also.

The interrupt() method is used to perform a group activity. Invoking interrupt() on a thread group results in invocation of interrupt() on all the threads under the thread group.

There are other methods which act on all child threads of a thread group, but these have been deprecated. These deprecated methods include suspend(), resume() and stop(). These methods have been deprecated in the Thread class also. These methods if used without synchronization have a lot of chance of leaving the objects in a corrupted state.

16.4 PROPERTIES OF Thread INSTANCE

name: Every thread can be given a name. The name may be specified in the constructor and we can also use the get and set methods given below:

    1     void setName(String name)
 2     String getName()

priority: Threads have priority. The priority of a thread is in the range from 1 to 10. There are constants in the Thread class called MIN_PRIORITY and MAX_PRIORITY. Another constant in the Thread class is the NORM_PRIORITY with a value of 5. This is the priority of the main thread when it is initially started by the JVM. The initial value of priority for a new thread will be decided by the priority of the thread which created the Thread.

At any point of time there are a limited number of processors available for execution of the various threads. The number of threads which are ready for execution may be more than the number of processors available to JVM. Which of the ready threads will be actually running is decided by a Thread Scheduler. The Thread Scheduler will decide which threads to be scheduled for execution based on their priorities. The one with the highest priority will be scheduled first. The thread class has methods for setting and getting the priority of a thread.

    1     void   setPriority(int priority)
 2     int    getPriority()

id (readonly): From Java 5 onwards, every thread has a 64-bit id value, which is unique to each thread within a JVM. This value identifies every instance of the Thread uniquely. We only have the getter method to get its value:

    1     public long getId()

ThreadGroup (readonly): Another property of a thread is its ThreadGroup. There is a class called ThreadGroup. Every Thread belongs to a ThreadGroup. The ThreadGroup of a thread is decided at the time of its creation. We cannot change the ThreadGroup of a Thread once it is created, i.e. we have only the getter method for ThreadGroup in the Thread class. There are constructors for the Thread class, which will allow us to specify the ThreadGroup.

    1     ThreadGroup   getThreadGroup()

16.4.1 Daemon Threads

Initially when the application starts, one of the ThreadGroup instances called main (whose name is main) exists. The main Thread belongs to this ThreadGroup. There can be other threads and thread groups in the JVM, which may be required for the working of the Java virtual machine’s activities, e.g. there will be some thread(s) to look after the garbage collection activity. The other threads and thread groups created depend on the JVM implementation and versions. Normally these threads which are required for other activities of the JVM are daemon threads; main is the only non-daemon thread.

daemon (settable only before start()): There are two types of threads, daemon and non-daemon. The Thread class has the methods of getting and setting the type of thread as given below:

    1     void   setDaemon(boolean daemon)
 2     boolean isDaemon()

The daemon property of the Thread can be set only before it is started using the start() method, i.e. once the thread of execution is created we cannot change the type of thread from daemon to non-daemon or vice versa. The JVM terminates once all non-daemon threads have finished, i.e. JVM does not wait for daemon threads to finish. Initially, when the application is started, there are at least two threads which are created, one is the main thread where our application runs, which is non-daemon. There is at least one more thread which is a daemon thread. Which is that thread? What else is executing while our main thread is running in the JVM? What happens when we have objects which do not have any reference? They are deallocated by the garbage collector. Garbage collector is a daemon thread. It simultaneously executes along with the main thread. The garbage collector is a continuous loop looking for Objects eligible for garbage collection and then deallocating them; it also calls the finalize() method before it deallocates these objects. The JVM does not wait for the garbage collector to finish. When the JVM starts, there are a few more non-daemon threads which are also created and which run within the JVM. Initially main is the only non-daemon thread in the JVM. So, in most of the applications, which we have seen untill now, we find that the JVM terminates only when the main() method finishes. If there were other non-daemon threads, which are still running when the main() method finishes, then the JVM would not terminate. Instead it would wait for the other non-daemon threads to finish execution.

When we call the constructor of Thread, the daemon property of the thread by default will be the daemon property of the thread which created the thread.

Stacktrace (readonly): Every live Thread instance has a runtime stack associated with it. There is a stack trace available from the Thread instance, which has an array of StackTraceElement. This is similar to the StackTraceElement[] available from the Throwable instance. Here also we have the getter method:

    1     public StackTraceElement[] getStackTrace();

There is also a static method getAllStackTraces() in the Thread class, which returns a mapping of all the active thread instances in the JVM with their stack traces. The method signature is as given below:

    1     public static Map<Thread, StackTraceelement[]> getAllStackTraces()

With this method we can get all the active threads running in the JVM along with information about their runtime stacks.

There are two ways in which the execution of a thread would finish. It either finishes successfully by returning from the method run() (except for the main thread where it returns from the method main()) or by throwing an exception.

What happens when a thread terminates with an exception? We have normally observed that when a thread terminates because of an exception, it normally prints the stack trace of the Throwable which was thrown out of the runtime stack on the standard error (console).

Normally, whenever a thread terminates because of an exception, it invokes the uncaughtException() method on the ThreadGroup instance to which the thread belongs. The ThreadGroup class has a method called uncaughtException() with two parameters. One is the Thread which has terminated and the other is the Throwable which is thrown out of the runtime stack. This implementation of the uncaughtException() method invokes the printStackTrace() on the Throwable instance.

uncaughtExceptionHandler: From Java 5 onwards, we have an interface called UncaughtExceptionHandler as a nested interface within the Thread class, which has the method uncaughtException() as given below:

    1     public uncaughtException(Thread th, Throwable t)

The ThreadGroup is also made to implement this UncaughtExceptionHandler interface with default implementation being to print the stack trace of the Throwable which caused the exception.

From Java 5 onwards, we have the ability to change this default behaviour for each individual Thread instance or even for all the threads in the JVM. This can be done by having some class implementing the UncaughtExceptionHandler interface and setting it as the uncaught exception handler for a specific thread or even for all threads in the JVM. For this we have the following methods in the Thread class:

    1     public UncaughtExceptionHandler getUncaughtExceptionHandler()
 2     public void setUncaughtExceptionHandler(
           UncaughtExceptionHandler handler)
 3     public static UncaughtExceptionHandler
           getDefaultUncaughtExceptionHandler()
 4     public void setDefaultUncaughtExceptionHandler(
           UncaughtExceptionHandler handler)

Now, whenever any thread terminates because of an exception, it first will be checked if an uncaught exception handler has been set for the individual thread instance or not. If an uncaught exception handler was set, then the uncaughtException() method on the uncaught exception handler for the thread will be invoked before the thread terminates. If for the thread which terminates because of an exception there is no uncaught exception handler set, then it will be checked if the default uncaught exception handler has been set for the JVM. If the default uncaught exception handler is set, then the uncaughtException() method of this handler will be invoked before the thread terminates. If for a thread which terminates because of an uncaught exception there is neither an uncaught exception handler set nor a default uncaught exception handler for the JVM, then in that case it will invoke the uncaughtException() on the ThreadGroup instance of the thread which has terminated. This was also the default behaviour till Java 1.4.

16.4.2 Thread States

The Thread instance maintains the state of its thread of execution. From Java 5 onwards there is an enum which defines the possible states for a thread of execution.

State (readonly): From Java 5, onwards, we have a method called getState() which returns the current state of the thread:

    1     public State getState()

From Java 5 onwards we have an enum called State inside the Thread class. This enum represents the various states of the Thread. The enum values for the State are as given below:

NEW,

RUNNABLE,

WAITING,

TIMED_WAITING,

BLOCKED,

TERMINATED

Initially, when a new instance of Thread is created, it is in the NEW state. When we call the start() method on the instance of Thread, the state changes to RUNNABLE, initially. The RUNNABLE state represents the state when the thread is either ready for execution where the thread scheduler may schedule it or it may be actually running. During the execution of the run() method by thread, the state of the thread may change to any state other than the NEW state. The TERMINATED state would indicate that the thread has finished its execution either successfully or with failure (with exception), and then there cannot be any further change in the state of a TERMINATED thread.

There are many ways in which the thread can change its state from RUNNABLE to WAITING and TIMED_WAITING states.

There are various methods in the Thread class, which change the state of the invoking thread to WAITING or TIMED_WAITING.

If a thread of execution wants to wait for another thread of execution to terminate then it can call the method join() on the thread instance which corresponds to the other thread of execution whose completion it wants to wait for:

    1     public void join()
 2     public void join(long milliseconds)
 3     public void join(long milliseconds, int nano)

When the join() method is invoked, then the state of the thread changes to the WAITING state. The invoker waits till the thread on which the method is invoked has finished. The join() method can have parameters to specify the timeout value. When join() is invoked with a timeout value, which may be specified either as milliseconds value or milliseconds plus nanoseconds value, then the state of the thread changes to the TIMED_WAITING state. In this case, the invoking thread waits for the other thread to finish but not longer than the timeout value specified. So, if the other thread does not finish execution within the specified timeout, then the invoking thread will resume execution with the statement after the invocation of join(). Here, it may be noted that there is no mechanism to know whether the join() method finished because of the completion of the other thread or because of timeout. We can simply check the state of the other thread, and if we find it to be in a TERMINATED state, then we can assume that the termination was because of completion of the thread.

If a thread of execution wants to have some delay before it can proceed to the next statement, then it can use the static method sleep(). The signature of the sleep() is as given in the listing below:

    1     public static void sleep(long milliseconds)
 2     public static void sleep(long milliseconds, int nano)

In all methods where timeout is specified using the nanoseconds parameter, the range for nanoseconds is always 0–999999. If the value of nanoseconds is outside this range then the methods will throw IllegalArgumentException. When a thread invokes the sleep() method, then its state changes to TIMED_WAITING. It would normally return to RUNNABLE state after the timeout value is passed and proceed with executing the next statements.

Let us summarize the various properties of the thread:

  • name

        1     String getname()
     2     void setName(String name)
  • priority

        1     int getPriority()
     2     void setPriority(int pri)

    The name and priority of a thread can be changed any time.

  • daemon

        1     boolean isDaemon()
     2     void setDaemon(boolean daemon)

    The daemon property cannot be changed once the Thread has started

  • id
        1     long getId()

    The id property is set when the instance is created.

  • ThreadGroup

        1     ThreadGroup getThreadGroup()

    The threadGroup property is initialized in the constructor of Thread and cannot be modified.

  • Alive

        1     boolean isAlive()

    This is set when we call the start method and reset once the thread terminates.

  • StackTrace

        1     StackTraceElement[] getStackTrace()

    The stackTrace property fetched at any point of time reflects the current picture of the runtime stack.

  • uncaughtExceptionHandler

        1     Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()
     2     void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler
              handler)

    The uncaughtExceptionHandler of a thread can be changed any time.

  • state

        1     Thread.State getState()

    The state property fetched at any point of time reflects the current state of the thread of execution.

Constructors of Thread: The various constructors of the Thread class are given in Listing 16.5.

 

Listing 16.5. Constructors of the Thread class

    1     Thread()
 2     Thread(ThreadGroup group, Runnable r)
 3     Thread(ThreadGroup group, String name)
 4     Thread(Runnable r)
 5     Thread(Runnable r, String name)
 6     Thread(String name)
 7     Thread(ThreadGroup group, Runnable r, String name)
 8     Thread(ThreadGroup group, Runnable r, String name, long stacksize)

All the constructors of Thread are various combinations of three parameters (a) thread-Group, (b) runnable and (c) name. There is only one constructor with four parameters, which has an additional parameter for the size of the runtime stack.

EXERCISE 16.1 Update the BankServer class in Exercise 15.1 to support multiple network clients simultaneously. The servicing of a client by the server may be done in a separate class which implements Runnable. This may be done on similar lines as making a multi-threaded echo server shown earlier.

Exercise 16.1 can be done by creating a separate class to service each client. We may call this class BankClerk. The listing for the BankClerk is given in Listing 16.6, and the corresponding BankServer is given in Listing 16.7.

 

Listing 16.6. BankClerk class implementing Runnable interface

    1 package bank;
 2
 3 import java.util.*;
 4 import java.util.regex.*;
 5
 6 public class BankClerk implements Runnable {
 7     private Bank bank;
 8     private Scanner commandScanner;
 9     private PrintWriter responseWriter;
 10    private static Matcher commandMatcher = Command.QUIT.getSyntax
           ().matcher("something");
 11
 12    public enum Command {
 13        OPEN("([Oo][Pp][Ee][Nn])\s+([SsCc])\s+(\w+)\s+(\d
                +(\.(\d)1,2)?)"),
 14        DEPOSIT("([Dd][Ee][Pp][Oo][Ss][Ii][Tt])\s+(\d+)\s+(\d
                +(\.(\d)1,2)?)"),
 15        WITHDRAW("([Ww][Ii][Tt][Hh][Dd][Rr][Aa][Ww])\s+(\d+)\s
                +(\d+(\.(\d)1,2)?)"),
 16        DISPLAY("([Dd][Ii][Ss][Pp][Ll][Aa][Yy])\s+(\d+)"),
 17        PASSBOOK("([Pp][Aa][Ss][Ss][Bb][Oo][Oo][Kk])\s+(\d+)"),
 18        LIST("([Ll][Ii][Ss][Tt])"),
 19        CLOSE("([Cc][Ll][Oo][Ss][Ee])\s+(\d+)"),
 20        SAVE("[Ss][Aa][Vv][Ee]"),
 21        QUIT("[Qq][Uu][Ii][Tt]"),
 22        ;
 23        private Pattern syntax;
 24        private Command(String pattern) {
 25            this.syntax = Pattern.compile(pattern);
 26        }
 27        public Pattern getSyntax() {
 28            return syntax;
 29        }
 30        public static Command getMatchingCommand(String input) {
 31            commandMatcher.reset(input);
 32            Command[] allCommands = Command.values();
 33            for (Command command : allCommands) {
 34                if (commandMatcher.usePattern(command.getSyntax()).
                       matches()) {
 35                    return command;
 36                }
 37            }
 38            return null;
 39        }
 40    }
 41    public BankClerk(Bank bank, InputStream in, OutputStream out)
           throws IOException {
 42        this.bank = bank;
 43        commandScanner = new Scanner(in);
 44        responseWriter = new PrintWriter(out, true);
 45    }
 46    public void service() throws NoSuchAccountException,
           NegativeAmountException, IOException {
 47        String input = commandScanner.nextLine();
 48        Command command = Command.getMatchingCommand(input);
 49        while (command != Command.QUIT) {
 50            int acno = 0;
 51            double amt = 0;
 52            char type = ’s’;
 53            String name = "";
 54
 55            if (command != null) {
 56                switch(command) {
 57                    case OPEN:
 58                        type = commandMatcher.group(2).charAt(0);
 59                        name = commandMatcher.group(3);
 60                        amt = Double.parseDouble(commandMatcher.
                              group(4));
 61                        switch(type) {
 62                           case ’S’:
 63                           case ’s’:
 64                               acno = bank.openSavingsAccount(name,
                                      amt);
 65                               break;
 66                           case ’C’:
 67                           case ’c’:
 68                               acno = bank.openCurrentAccount(name,
                                      amt);
 69                        }
 70                        bank.display(acno, responseWriter);
 71                        break;
 72                    case DEPOSIT:
 73                        acno = Integer.parseInt(commandMatcher.
                              group(2));
 74                        amt = Double.parseDouble(commandMatcher.
                              group(3));
 75                        bank.deposit(acno, amt);
 76                        bank.display(acno, responseWriter);
 77                        break;
 78                     case WITHDRAW:
 79                        acno = Integer.parseInt(commandMatcher.
                              group(2));
 80                        amt = Double.parseDouble(commandMatcher.
                              group(3));
 81                        if (bank.withdraw(acno, amt)) {
 82                            bank.display(acno, responseWriter);
 83                        } else {
 84                            responseWriter.println("Warning:
                                   Insufficient balance");
 85                        }
 86                        break;
 87                     case DISPLAY:
 88                        acno = Integer.parseInt(commandMatcher.
                              group(2));
 89                        bank.display(acno, responseWriter);
 90                        break;
 91                     case PASSBOOK:
 92                        acno = Integer.parseInt(commandMatcher.
                              group(2));
 93                        bank.printPassbook(acno, responseWriter);
 94                        break;
 95                     case LIST:
 96                        bank.listAccounts(reseponseWriter);
 97                        break;
 98                     case CLOSE:
 99                        acno = Integer.parseInt(commandMatcher.
                              group(2));
 100                       amt = bank.close(acno);
 101                       responseWriter.println(amt+" is the closing
                              balance");
 102                       break;
 103                    case SAVE:
 104                       bank.save();
 105                       responseWriter.println("Bank Object saved");
 106                       break;
 107                 }
 108             } else {
 109                 responseWriter.println("Invalid command");
 110             }
 111             input = commandScanner.nextLine();
 112             command = Command.getMatchingCommand(input);
 113        }
 114    }
 115    public void run() {
 116        try {
 117             service();
 118        } catch (Exception e) {
 119             e.printStackTrace();
 120        }
 121    }
 122 }

 

Listing 16.7. Multi-threaded BankServer class

    1 package bank;
 1
 3 import java.util.*;
 4 import java.util.regex.*;
 5
 6 public class BankServer {
 7     private Bank bank;
 8     private ServerSocket ss;
 9
 10    public BankServer(int portNo, Bank bank) throws IOException {
 11        this.bank = bank;
 12        ss = new ServerSocket(portNo);
 13    }
 14    public void run() throws IOException {
 15        while (true) {
 16            Socket socket = ss.accept();
 17            BankClerk clerk = new BankClerk(bank, socket.
                  getInputStream(), socket.getOutputStream());
 18            Thread thread = new Thread(clerk);
 19            thread.start();
 20        }
 21    }
 22    public static void main(String[] args) throws Exception {
 23        int portNo = Integer.parseInt(args[0]);
 24        String bankName = args[1];
 25        String bankFileName = bankName + ".bank";
 26        File bankFile = new File(bankFileName);
 27        Bank bank = null;
 28        if (bankFile.exists()) {
 29            bank = Bank.load(bankName);
 30        } else {
 31            int bankCode = Integer.parseInt(args[2]);
 32            bank = new Bank(bankName, bankCode);
 33        }
 34        BankServer server = new BankServer(portNo, bank);
 35        server.run();
 36    }
 37 }

Other methods of the Thread class: A thread of execution can terminate another thread of execution by calling method destroy() or stop(). The stop() method is deprecated. It is not advisable to terminate threads by using stop() and destroy() methods.

The static method dumpStack() prints the current state of the runtime stack similar to the printStackTrace() in the Throwable. In fact the dumpStack() uses the printStackTrace() method.

Thread class has methods called suspend() and resume() which are deprecated. When a thread of execution wants to temporarily stop the execution of another thread of execution, it can call the method suspend on the other thread and then can later call resume() to allow it to proceed with the execution.

The three methods related to an interrupted flag are:

    1            void interrupt()
 2            boolean isInterrupted()
 3     static boolean interrupted()

The interrupt() method when invoked on a thread instance will set the interrupted flag; the method isInterrupted() can be used to check the value of the interrupted flag. The method interrupted() which is a static method can be used by the current thread to read the value of the interrupted flag as well as to reset its value to false.

When a thread is in a wait state because of wait(), join() or sleep() then invoking interrupt() on these threads will cause the methods to throw an InterruptedException.

Let us consider a class Account with deposit() and withdraw() methods as given below:

    1     public class Account {
 2       …….
 3       double balance ;
 4       …….
 5       public void deposit(double amt) {
 6           ………
 7           balance += amt;
 8           ………
 9       }
 10       ……..
 11       public boolean withdraw(double amt) {
 12          ……..
 13          if (balance < amt) {
 14             return false;
 15          }
 16           ………
 17           balance -= amt;
 18           ………
 19        }
 20
 21    }
16.5 SYNCHRONIZATION

Now in a multi-threaded environment, if the withdraw() method is being invoked by two threads almost simultaneously, let us assume that initially a particular account object has a balance of 1500. In addition, these two threads try to withdraw 1000 each. It could happen that the first thread has done the checking in Line 13 but it has not yet updated the balance in Line 17, and at that point of time the second thread reaches Line 13. What will happen in such a situation? What happens in the real bank where there are two tellers, and two friends give two cheques for withdrawal from the same account at the same time to both the tellers? When the first teller starts the withdrawal process on the account, he first puts his tag on the account indicating that he is currently working on this account. Only after he has completed updating the balance will he remove his tag. The second teller, during this time has to wait till the tag is removed. He will not touch the account till the tag of the other teller is there on the account.

In Java every Object has a lock, which can be obtained by any executing thread by using the keyword synchronized. The keyword synchronized is used for obtaining a lock on an Object. Inside a method we can have a synchronized block. The syntax for writing a synchronized block is:

synchronized(<reference expression>) {

  //reference expression is refering to an Object

          ………… <code in the synchronized block

      }

Once a thread of execution reaches a synchronized block it has to first obtain the lock on the object referred in the reference expression. To obtain the lock it will have to wait for the lock to be released by any other thread which could have already been holding the lock by using a synchronized block. The lock is automatically removed once the synchronized block is over. The synchronized block can be used inside any method to obtain a lock on any object. In Java a method could also be declared synchronized in which case it is like the entire method is in a synchronized block. When the method is synchronized, the lock is obtained of the object on which the method is invoked. In case of the static synchronized method, the lock is obtained on the class instance.

So in our example we could declare the two methods deposit() and withdraw() to be synchronized since some updation is taking place in these methods.

A thread can use the static method holdsLock() to find out if it currently has a lock on a given object.

    1     static boolean holdsLock(Object o)

Assume that the deposit() and withdraw() methods in the above case are synchronized. Now if we go to a bank for withdrawal and we give our friend some cash to be deposited, we may not know whether he has deposited the cash or not. When we try to withdraw, the teller tells us that there is insufficient balance. At this point we may be prepared to wait for the deposit. This can be achieved by modifying the withdraw() method as

13     if (balance < amt) {
14         wait();
15     }

The wait() method does two things. It not only goes into the wait state but it also releases the lock which it holds. The wait method can be invoked by a thread only on an object for which it has a lock. So wait() can be invoked only from within a synchronized code which has been synchronized on the object on which we want to invoke the wait() method. Calling wait() on an object for which we do not have a lock would throw and IllegalStateException. What is the thread waiting for? It is waiting for a notification on the object by some other thread. So we could have the deposit method as follows:

5     public void deposit(double amt) {
6         ………
7         balance += amt;
8         ………
8.1       notify();
9     }

Again like wait() method the notify() method can be invoked only on the object for which the thread has a lock, i.e. it can only be invoked from the synchronized code.

In the above code, what will happen if let us say initially, the account has a balance of 1500, and we try to withdraw 5000, and the deposit of 1000 takes place on that account. Because the first thread gets a notification it will proceed with the next instruction after wait() and will proceed to update the balance, which would not be correct. The checking of balance should be done before proceeding with any other thing. Hence the if statement in Line 13 is normally a while loop like:

13         while (balance < amt) {
14             wait();
15         }

Normally wait() is used inside a while loop waiting for some favourable condition so that it can proceed.

Again take a case when initially the balance in an account is 1500 and the first thread tries to withdraw 5000 and goes into wait, the second thread tries to withdraw 3000 and goes into wait and some other threads also try to withdraw more than the balance and go into the wait state. There are a number of threads which may be waiting for notification on the same object. Now, suppose a deposit of 2000 is done, and the first thread gets the notification. In such a case the first thread will again be in wait. Here the second thread only wants 3000 and this could be fulfilled if it had got the notification. When notify() is called on an object, the notification can go to any one of the waiting threads. There is nothing in the JVM specification about the order in which the notification should be given. Here though after a deposit there is sufficient balance for the second thread to proceed, it could remain in the wait state because it did not get the notification (some other thread may have got the notification). Also in case the deposit is for 50000 in which case all the waiting threads could have been withdrawn, all except one will remain in the wait state. The Object class has also got a method called notifyAll(). Invoking notifyAll() on an object results in the notification reaching all the threads waiting on the object for the notification.

So deposit could be modified as:

5     public void deposit(double amt) {
6         ………
7         balance += amt;
8         ………
8.1       notifyAll();
9     }

Methods of Object class:

    1         void wait()
 2         void wait(long milli)              // with timeout
 3         void wait(long milli, int nano)    // with timeout
 4         void notify()
 5         void notifyAll()
16.6 ANOTHER WAY OF CREATING A THREAD OF EXECUTION

Let us look at the API for the Thread class. We find that the thread class implements Runnable and so it has a default run() method. The default run() method simply checks if there is any target Runnnable assigned to it through the constructor. If the target Runnable is not null then it invokes the run() method on the Runnable and this is how the run() method of Runnable is invoked. When we call the start() method on a thread it is the run() method of this thread which is executed on the new runtime stack.

So another way of creating a new thread of execution is to sub-class from the Thread class and override its run() method. So in such a case the steps for creating a new thread of execution would be:

  1. To create an instance of a sub-class of Thread, where the run() method has been overridden.
  2. To set the properties for the new thread of execution by calling the setter methods on it.
  3. To call the start() method on this instance of sub-class of the thread.

Creating a separate class implementing the Runnable is the preferred way of creating a new thread of execution. The sub-classing of Thread is not preferred. The run() method of the Runnable is used to specify the work to be done and the Thread class is used to do the work.

i.e. Runnable is the work and Thread is the worker.

LESSONS LEARNED
  • For each thread of execution, there is a separate runtime stack, where frames are created for each method or constructor invocation. The frame has space for the local variables.
  • Corresponding to each thread of execution, there is a unique instance of Thread associated with it. This instance of Thread enables other threads of execution to know the status of this thread of execution and also interact with it.
  • A new thread of execution is created by creating an instance of a class which implements the Runnable interface using it to create an instance of the Thread class, optionally setting up the properties on the thread instance and then calling the start() method on the Thread instance.
  • The various instances of Thread are organized in a hierarchial structure by using the ThreadGroup class. Each ThreadGroup has a parent ThreadGroup and contains various Thread instances.
  • Each Thread instance has various properties like name, priority, uncaught-ExceptionHandler, etc. These can affect the way the execution of the corresponding thread of execution takes place.
  • The state of a thread is available by using the getState(), which returns an enum value available from Thread.State.
  • A thread can wait for another thread to finish by invoking the join() method on the Thread instance whose completion it wants to wait for.
  • All instances in Java have a lock. A thread of execution automatically obtains the lock on an instance when executing a synchronized block with the target instance. If the lock is not available (since some other thread may have it), then these threads are blocked till the lock becomes available.
EXERCISES
  1. State which of the following are true or false:
    1. JVM waits for all daemon threads to terminate.
    2. Inside a synchronized method, one can assume that no other threads are currently executing any other method of the same class.
    3. The sleep() method of the Thread class can be used on an instance of Thread to send the target thread object into the wait state (like TIMED_WAITING state).
    4. When a thread executes a static synchronized method, then another thread calling the same method would have to wait for completion of the executing method.
    5. When a thread executes a non-static synchronized method, then in all circumstances, another thread calling the same method would have to wait for completion of the executing method.
    6. Whenever a new Thread Object is created using its constructor, then it is non-daemon by default unless it is explicitly set as daemon by calling the setDaemon() method.
    7. Whenever a new Thread Object is created using its constructor, then its priority is Thread.NORM_PRIORITY(5) by default unless it is explicitly set to a different value by calling the setPriority() method.
    8. The static method getAllStackTraces() in the Thread class returns a Map containing live threads in the currentThread’s ThreadGroup only.
  2. Fill in the blanks in the following:
    1. notifyAll() method is defined in class __________.
    2. The call to Thread.sleep(2000); puts the invoking Thread into which __________ state? (Specify the enum value of the state.)
    3. Assuming that a Thread th is alive the call to join(2000); puts the Thread into __________ state?
    4. The call to a synchronized method on an instance whose lock is not currently with the calling thread puts the Thread into __________ state?
    5. Invocation of the join() method with timeout on a thread puts the invoker into __________ state (specify the enum value of Thread.State).
  3. Explain the use of keyword synchronized.
  4. List the possible enum values which may be returned by the getState() method of the Thread class.
  5. Which of the following threads is sent into the wait state by invocation of the sleep method?
    1. Thread given as parameter
    2. The thread calling the method
    3. The main thread
  6. Which of the following methods are deprecated?
    1. public void wait()
    2. public void suspend()
    3. public static void sleep(long millis)
    4. public void stop()
    5. public void join(long millis)
    6. None of the above
  7. Explain with an example why the suspend(), resume() and stop() methods are deprecated?
  8. Explain the relation between the Thread instance and the thread of execution.
  9. Explain how instances of Thread are organized in a hierarchial manner using the ThreadGroup class.
  10. Explain the difference between daemon and non-daemon threads.
  11. Explain the two different ways of creating a new thread of execution. Which of these is the preferred and why?
  12. Write a general-purpose TCP Client, which takes two command line arguments, a host name or address and a port number. It should make a socket connection with the server and then display all the input coming from the socket on the standard output and simultaneously listen for input coming from the standard input and send it to server. Hint: Using Echoer class of Listing 16.1 may be useful.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.233.43