Tuesday 30 July 2013

Understanding IllegalThreadStateException, IllegalMonitorStateException and thread states

There was a question on IllegalMonitorStateException and thread states in CodeRanch (http://www.coderanch.com/t/616837/java-programmer-SCJP/certification/state). Since it is a general topic of interest to most OCPJP7 aspirants, I'm posting the expanded version of the answer I gave as blog entry here.  


Basic thread states 

A thread has various states during its lifetime. Three basic thread states to understand are – new, runnable and terminated. We will discuss more thread states a bit later. 

A program can access the state of the thread using Thread.State enumeration. The Thread class has the getState() instance method which returns the current state of the thread. Here is an example: 

class BasicThreadStates extends Thread {  
    public static void main(String []s) throws Exception {  
        Thread t = new Thread(new BasicThreadStates());  
        System.out.println("Just after creating thread; \n" +   
                "   The thread state is: " + t.getState());   
        t.start();  
        System.out.println("Just after calling t.start(); \n" +   
                "   The thread state is: " + t.getState());  
        t.join();   
        System.out.println("Just after main calling t.join(); \n" +   
                "   The thread state is: " + t.getState());  
    }  
}  

This program prints: 

Just after creating thread;   
    The thread state is: NEW  
Just after calling t.start();   
    The thread state is: RUNNABLE  
Just after main calling t.join();   

    The thread state is: TERMINATED  

Just after the creation of the thread and just before calling the start() method on that thread, the thread is in the new state. After calling the start() method, the thread is ready to run or is in the running state (which we cannot determine); so it is in runnable state. From the main() method, we are calling t.join(). The main() method waits for the thread t to die. So once the statement t.join() successfully gets executed by main() thread, it means that the thread t has died or terminated. So, the thread is in the terminated state now. 

A word of advice: be careful about accessing the thread states using the getState() method. Why? By the time you acquire information on a thread state and print it, the state could have changed! I know the last statements could be confusing. To understand the problem with getting thread state information using the getState() method, consider the previous example. In one sample run of the same program, it printed the following: 

Just after creating thread;   
        The thread state is: NEW  
Just after calling t.start();   
        The thread state is: TERMINATED  
Just after main calling t.join();   

        The thread state is: TERMINATED  

Note the red italicized part of the output, the statement after printing “Just after calling t.start();”. In the initial output, we got the thread state (as expected) as RUNNABLE state. However, in another execution of the same program without any change, it printed the state as TERMINATED. Why? In this case, the thread is dead before we could get a chance to check it and print its status! [Note that we have not implemented the run() method in the BasicThreadStates class, so the default implementation of the run() method does nothing, and terminates quickly.] 


More thread states 

A thread can also be in blocked, waiting, timed_waiting states—which we’ll discuss now. I've attached a figure with this post which shows how and when the state transitions typically happen for these six states. You can use Thread.State enumeration which has the list of possible thread states. Here is a simple program that prints the value of the states in this enumeration: 

class ThreadStatesEnumeration {  
    public static void main(String []s) {  
        for(Thread.State state : Thread.State.values()){  
            System.out.println(state);  
        }  
    }  

}  

It prints: 
NEW  
RUNNABLE  
BLOCKED  
WAITING  
TIMED_WAITING  

TERMINATED  


Now let us discuss exceptions. 

IllegalThreadStateException 

Here is a program that throws IllegalThreadStateException

class ThreadStateProblem {
                public static void main(String []s) {
                                Thread thread = new Thread();
                                thread.start();
                                thread.start();
                }
}

The program fails with this stack trace:

Exception in thread "main" java.lang.IllegalThreadStateException
                at java.lang.Thread.start(Unknown Source)
                at ThreadStateProblem.main(ThreadStateProblem.java:6)


Here, we are trying to start a thread that has already started. When we call start(), the thread moves to “new” state. There is no proper state transition from “new” state if we call start() again; so the JVM throws IllegalThreadStateException

IllegalMonitorStateException

Here is a program that results in a IllegalMonitorStateException: 

class ThreadStateProblem extends Thread {
                public void run() {
                                try {
                                                wait(1000);
                                }
                                catch(InterruptedException ie) {
                                                // its okay to ignore this exception since we’re not
                                                // interrupting exceptions in this code
                                                ie.printStackTrace();
                                }
                }
               
                public static void main(String []s) {
                                new ThreadStateProblem().start();
                }
}

This program crashes like this:

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
                at java.lang.Object.wait(Native Method)
                at ThreadStateProblem.run(ThreadStateProblem.java:4)

The wait(int) method (with or without timeout value) should be called only after acquiring a lock: a wait() call adds the thread to the waiting queue of the acquired lock. If we don’t do that, there is no proper transition from the running state to timed_waiting (or waiting state in case timeout value in not given) can happen. So, the program crashes by throwing IllegalMonitorStateException exception.

The correct fix is to acquire the lock before calling wait(). In this case, we can declare the run() method synchronized:

synchronized public void run() {
                try {
                                wait(1000);
                }
                catch(InterruptedException ie) {
                                // its okay to ignore this exception since we’re not
                                // interrupting exceptions in this code
                                ie.printStackTrace();
                }
}

Since the run() method is synchronized, the wait() will add itself to the this object reference lock. Since there is no one calling the notify()/notifyAll() method, after timeout of 1 second (1000 milliseconds) is over, it will return from the run() method. So, the wait(1000); statement behaves almost like sleep(1000) statement; the difference is that calling wait() releases the lock on this object when it waits while sleep() call will not release the lock when it sleeps.

So, the key observation is that: We must call wait and notify/notifyAll only after acquiring the relevant lock. 

Difference between these exceptions 

If you already did not recognize,  IllegalThreadStateException is different from IllegalMonitorStateException. 

Here is the description of IllegalThreadStateException from JavaDoc: "Thrown to indicate that a thread is not in an appropriate state for the requested operation. See, for example, the suspend and resume methods in class Thread." 

Here is the description of IllegalMonitorStateException from JavaDoc: "Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor." 

In both the cases, we need to be careful about thread states. In case of IllegalThreadStateException, it is about attempted illegal transition in thread states (and is nothing to do with locks). Whereas with IllegalMonitorStateException, it is about holding a lock - it occurs when attempting to call methods such as wait or  notify on an object that does not hold the lock. 

No comments:

Post a Comment