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.1–11.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.
Thread
CLASS AND THREAD OF EXECUTIONIn 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.
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:
Runnable
and has an implementation of the run
() method.Thread
class using the Runnable
created in Step 1. One of the constructors in the Thread
class takes Runnable
as a parameter.Thread
instance created in Step 2. This is an optional step, since we may not want to change the initial values of the properties.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 }
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.
Thread
INSTANCEname
: 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()
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.
Thread
StatesThe 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 }
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()
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:
Thread
, where the run()
method has been overridden.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.
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.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.Thread
are organized in a hierarchial structure by using the ThreadGroup
class. Each ThreadGroup
has a parent ThreadGroup
and contains various Thread
instances.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.getState
(), which returns an enum
value available from Thread.State.join
() method on the Thread
instance whose completion it wants to wait for.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.synchronized
method, one can assume that no other threads are currently executing any other method of the same class.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).static synchronized
method, then another thread calling the same method would have to wait for completion of the executing method.synchronized
method, then in all circumstances, another thread calling the same method would have to wait for completion of the executing method.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.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.static
method getAllStackTraces
() in the Thread
class returns a Map
containing live threads in the currentThread’s ThreadGroup
only.notifyAll
() method is defined in class __________.Thread
into which __________ state? (Specify the enum value of the state.)Thread th
is alive the call to join(2000); puts the Thread
into __________ state?synchronized
method on an instance whose lock is not currently with the calling thread puts the Thread
into __________ state?join
() method with timeout on a thread puts the invoker into __________ state (specify the enum value of Thread.State).synchronized
.getState
() method of the Thread
class.public void wait()
public void suspend()
public static void sleep(long millis)
public void stop()
public void join(long millis)
suspend
(), resume
() and stop
() methods are deprecated?Thread
instance and the thread of execution.Thread
are organized in a hierarchial manner using the ThreadGroup
class.Echoer
class of Listing 16.1 may be useful.18.191.233.43