What is deadlock?

A deadlock is a situation in which two or more processes are waiting indefinitely for an event to happen, and that event can only be caused by one of these waiting processes, but none of these processes can cause that event because they are in a wait state. 

In a multithreaded environment, because multiple threads are running concurrently, the deadlock is possible. Several situations can give rise to a deadlock. This post discusses a very simple situation, coded in below Listing, in which two threads, t1 and t2, are sharing the resources resourceA and resourceB. These threads access these resources in reverse order (lines 5 and 6). Thread t1 seeks a lock on obj1 and then on obj2, while thread t2 seeks a lock on obj2 and then on obj1 (lines 5, 6, and 20 through 28). Although the lock access code that is executed for each thread is the same (lines 20 through 28), resource A means obj1 for thread t1 and obj2 for thread t2, and resourceB means obj2 for thread t1 and obj1 for thread t2 due to lines 5, 6, 16, and 17.



DeadLockTest.java

1. public class DeadLockTest {
2.      public static void main(String[] args) {
3.           Object obj1 = "objectA";
4.           Object obj2 = "objectB";
5.           DeadLock t1 = new DeadLock(obj1, obj2);
6.           DeadLock t2 = new DeadLock(obj2, obj1);
7.           t1.start();
8.            t2.start();
9.            System.out.println("The threads have been started");
10.    }
11. }

12. class DeadLock extends Thread {
13.     private Object resourceA;
14.     private Object resourceB;
15.   public DeadLock(Object a, Object b){
16.        resourceA = a;
17.        resourceB = b;
18. }

19.   public void run() {
20.      while (true) {
21.      System.out.println("The thread " + 
                        Thread.currentThread().getName()
                         + " waiting for a lock on " + resourceA);
22.      synchronized (resourceA){
23.      System.out.println("The thread " + Thread.currentThread().getName()
                             + " received a lock on " + resourceA);
24.       System.out.println("The thread " + Thread.currentThread().getName()
                             + " waiting for a lock on " + resourceB);
25.        synchronized (resourceB){
26.        System.out.println("The thread " +
                    Thread.currentThread().getName() + " received a lock on " +
                    resourceB);

27.            try{
28.              Thread.sleep(500);
29.           }catch (Exception e){}
30.        }
31.     }
32.    }
33.   }
34. }

When you execute this code, the output is not deterministic. However, it is easy to see from lines 20 through 30 that eventually a deadlock will happen because t1 will not release a lock on obj1 until it receives a lock on obj2, whereas t2 will not release a lock on obj2 until it receives a lock on obj1. Following is one of the outputs that you may receive:

     The threads have been started
    The thread Thread-0 waiting for a lock on objectA
    The thread Thread-0 received a lock on objectA
    The thread Thread-1 waiting for a lock on objectB
    The thread Thread-0 waiting for a lock on objectB
    The thread Thread-1 received a lock on objectB
    The thread Thread-1 waiting for a lock on objectA



Reference(s): SCJP Exam for J2SE 5

Thread Scheduling in Java

In Java you can use methods to help a thread get into the runnable state, but you cannot directly put the thread into the running state. A thread in the runnable state is eventually put into the running state by the scheduler. The scheduler follows some kind of algorithm to determine which thread should go into the running state. 

There are two main categories of scheduling algorithms:

Preemptive scheduling: 

In this type of scheduling a thread is put into the running state according to the priority. While a thread is running, it can be moved out of the running state only by one of the following conditions:
  • It calls one of the blocking I/O methods.
  • A higher-priority thread gets into the runnable state.


Time sharing: 

In this kind of scheduling algorithm, a thread is allowed to execute only for a limited amount of time. After the time expires, it’s put back into the runnable state, and another thread seeking its turn is put into the running state. Time sharing provides protection against a higher-priority thread using the whole CPU time and keeping all other threads from running. 

Remember that the implementation of the scheduling algorithm in Java is platform dependent. Scheduling is performed to share a resource (the CPU) among multiple threads. Other resources in an application, such as a file, are shared properly by synchronizing the access, as you have already seen. However, sharing the same resource makes threads dependent upon each other, and that dependency may give rise to a deadlock.

If you are curious about deadlock and want to know about it, You may find detail about deadlock here. What is deadlock?


Reference(s): SCJP Exam for J2SE 5

Thread Lifecycle in Java

A thread’s life starts when the method start() is invoked on it. Subsequently, it goes through various states before it finishes its task and is considered dead. Upon calling the start() method, the thread does not start running immediately. The start() method puts it into the runnable state, also called the ready state. It stays in the runnable state until the scheduler puts it into the running state. When the thread goes into the running state, the method run() is called.

Remember that: When the start() method is invoked on a thread, it does not start running immediately; it has to go through a scheduler.

Even during the execution of the thread run() method, the thread may temporarily stop executing and go into one of the nonrunning states, and eventually come back to the running state. The various states of a thread are listed here:

• New: This is the state of a thread when it has been instantiated but not yet started.

• Ready/runnable: A thread enters the runnable state for the first time when the start() method is invoked on the thread instance. Later, the thread can come back to this state from one of the nonrunnable states. In the runnable state, the thread is ready to run and waiting to be selected by the scheduler for running.

• Running: This is the state in which the thread is executing.



Non-runnable states of thread: A thread in the running state may go into one of the three nonrunnable states when it is still alive but not eligible to run. These three states are listed here:
  • Blocked: A thread goes into this state when it’s waiting for a resource such as I/O or anobject’s lock. The availability of the resource will send it back to the runnable state. You will learn about the object’s lock later in this chapter. 
  • Sleeping: This state is one of the timed waiting states because the thread stays in this state for a specific time. A thread goes into this state when its code tells it to sleep for a specific period of time by calling the sleep(…) method. Expiration of the sleep time sends it back to the runnable state.
  • Waiting: The thread goes into the waiting state when the object on which it’s running invokes the wait() method. A call to notify() or notifyAll() (by another thread) will bring it back to the runnable state.
• Dead: A thread is considered dead after the execution of its run() method is complete. A dead thread can never be run as a separate thread again—that is, if you call its start() method, you will receive a runtime exception. However, it is still an object, and you can still call its methods (other than the start() method), but they will be executed in the caller’s thread.

Below figure shows the relationship among all these states of a thread. A running thread may go into blocked or sleeping state and wait there for an event to happen. When the right event happens, the thread goes to the ready state and eventually is put into the running state again by the scheduler.

Different states of a thread


Reference(s): SCJP Exam for J2SE 5