Java Multithreading Interview Questions

Java Multithreading Interview Questions | In Java, multithreading is one of the most important areas to ask questions in the interview therefore we have prepared more questions and answers on Java multithreading.

Table of Contents

Java Multithreading Introduction Interview Questions

The process of executing multiple tasks at a time concurrently is called multitasking. Multithreading is used to obtain multitasking. It consumes less memory and gives an efficient performance.

The three different flows of executing tasks:-

  1. Sequential flow of execution:- Two tasks are executed one after one. It means the second task execution is started only after the first task execution is fully completed.
  2. Parallel flow of execution:- Two tasks are executed at a time without depending on each other. It means at the same point of time two tasks can be in running mode.
  3. Concurrent flow of execution:- Two tasks are executed simultaneously (at a time concurrently). It means the second task will be run only when the first task execution is paused. Similarly, first task execution is resumed when the second task is paused.

There are two types of multitasking:-

  1. Process based multitasking
  2. Thread based multitasking

Executing several tasks simultaneously where each task is a separate independent process (or program) is called process-based multitasking.

Example:- Assume while typing the Java program in a text editor, you are also listening to music, and downloading a file. Here coding in text editor, music system player, and downloading a file will be executed simultaneously but they are independent of each other. Process-based multitasking is best suitable at the OS (operating system) level but not at the programmatic level.

Executing several tasks simultaneously where each task is a separate independent part of the same program is called thread-based multitasking. It is best suitable at the programmatic level.

Thread is an independent sequence flow of execution. It executes the method in sequence one after one. Each thread runs in a different stack frame. A process may contain multiple threads. Threads share the process resources, but still, they execute independently.

Multithreading is a process of creating multiple threads for executing multiple independent tasks concurrently to complete their execution in less time by using CPU ideal time effectively.

The main advantage of multithreading is to increase the performance of the system by reducing response time. Its other advantages are:

  • Multithreading provides better utilization of cache memory as threads share the common memory resources.
  • Multithreading reduces the number of the required server as one server can execute multiple threads at a time.
  • Multithreading allows an application/program to be always reactive for input, even already running with some background tasks
  • Multithreading allows the faster execution of tasks, as threads execute independently.
MultitaskingMultithreading
The process of executing multiple independent main tasks at a time concurrently is called multitasking.The process of executing multiple independent sub-tasks in one main task at a time is called multithreading.
Each task is executed in a separate process.All tasks are executed in a single process.
Multitasking is heavyweight because switching between contexts is slow. Here each task is executed in a separate process which is created at a different address.Multithreading is lightweight because switching between contexts is fast. Here all threads are created in a single process means threads are created in the same address.
Multitasking exists at the OS level. Example:- Windows, Linux, Mac.Multithreading exists at the process level. Example:- JVM.

Yes, by default Java is a multithreaded programming language. JVM creates a “main” thread group that creates a “main” thread. The “main” thread is responsible for calling the main() method. Not only the main thread but JVM also creates many daemon threads that run in the background to provide services to the non-daemon threads. See more:- Daemon thread in Java

Thread Scheduler is part of JVM and it is responsible for scheduling thread execution. If multiple threads are waiting to get the chance of execution then in which order threads should be executed is decided by the thread scheduler.

The main task of the thread scheduler is:- Scheduling the thread execution.

If the entire application is executed by a single thread itself then it is called a single thread model application. But if the application is executed by multiple threads then it is called a multi-thread model application.

In a single thread flow of execution, if one task execution is paused, this paused time will not be used for executing other tasks. Here execution time will be wasted. Due to this waste of paused time, program execution takes more time. Using multithreading one task paused time will be allocated to execute another task so that program execution will be fast.

To complete independent multiple tasks execution in less time we should create multiple threads. In multithreading-based programming, CPU ideal time is utilized effectively.

The main thread is used for executing Java methods logic by providing separate memory blocks in it. This memory block is technically called a stack frame. As many methods and constructors invoke those many new stack frames are created. Once the current method and constructor logic are executed, the stack frame is destroyed.

The garbage collector is used for destroying unreferenced objects from the heap area, and freeing this memory, and returning it to the JVM. So, JVM will use this free memory for creating new objects.

In Context switching the state of the process (or thread) is stored so that it can be restored and execution can be resumed from the same point later. Context switching enables multiple processes to share the same CPU.

Yes, in multithreaded programming every thread maintains its own or separate stack area in memory due to which every thread is independent of each other.

Defining Threads in Java – Multithreading

We can define a thread in the following 2 ways,

  1. By extending Thread class.
  2. By implementing Runnable interface.

Defining a thread in Java by extending from Thread class,

// defining a thread extending from Thread class
class MyThread extends Thread {
   // override run() method
   @Override
   public void run() {
      // code
   }
}
// Testing
public class ThreadDemo {
   public static void main(String[] args) {
      // thread instantiation
      MyThread t = new MyThread();
      t.start(); // child thread start
   }
}

Defining a thread by implementing the Runnable interface,

// defining a thread by implementing Runnable interface
class MyThread implements Runnable {
   // implement run() method
   @Override
   public void run() {
      // code
   }
}
// Testing
public class ThreadDemo {
   public static void main(String[] args) {
      MyThread mt = new MyThread();
      MyThread t = new MyThread(mt);
      t.start(); // child thread start
   }
}

Defining a thread by implementing the Runnable interface is better and recommended to use.

In the first approach (by extending from Thread class) our class already extends from the Thread class therefore there is no chance of extending it from any other class. In this way, we will miss inheritance benefits. 

In the 2nd way (by implementing Runnable interface) our class can extend from any class. And due to this reason, we will have inheritance benefits. Therefore in these two ways, the 2nd way i.e. “implementing from Runnable interface” is recommended. The 1st way also gives performance issues. 

The start() method. If we call the run() method then it will be executed as a normal method called, and a new thread will not be created.

Thread class start() method is responsible for,

  1. Register the thread with thread scheduler.
  2. Perform all other mandatory activities.
  3. Invoke run() method.

Hence, without executing the thread class start() method there is no chance of starting a new thread in Java.

No, the start() method has to perform some important activities like- register the thread, perform all other mandatory activities. After completing these operations it will invoke the run() method, and then thread execution will be started.

MyThread t = new MyThread();
t.start();
t.run();

In the case of the t.start() method, a separate thread will be created which will be responsible for the execution of the run() method. But in the case of the t.run() method, it will be a normal method call. A new thread will not be created, and the run() method will be executed by the main thread itself.

Yes, overloading of the run() method is possible but the Thread class start() method always invokes the run() method with no argument. To execute another overloaded form of the run() method we have to call it explicitly like a normal method call. 

class MyThread extends Thread {
   // start() method always call run()
   @Override
   public void run() {
      // call other run(-) method from here
      run(7.5);
   }

   public void run(double n) {
      // code
   }
}

Note:- Overloading of the main() method is also possible but the main thread always calls:- “public static void main(String[] args)” method. Other overloaded forms of main() will be treated like normal methods, and to execute them we have to call them explicitly.

class MyThread extends Thread { }
public class ThreadDemo {
   public static void main(String[] args) {
      MyThread t = new MyThread();
      t.start(); 
   }
}

Since the run() method is not available in the child class (MyThread) therefore it will be executed from the parent class (Thread class). The run() method of the Thread class has an empty implementation. Therefore the program will run successfully but this child thread won’t give any output.

class MyThread extends Thread {
   @Override
   public void start() {
      // code
   }
   @Override
   public void run() {
      // code
   }
}

Since we are overriding the start() method in the MyThread class, therefore the start() method of the Thread class will not get a chance to execute. In this case, the start() method of MyThread will be executed like a normal method call. A new thread will not be created. Complete execution will be done by only the main thread, therefore output will be fixed. 

class MyThread extends Thread {
   @Override
   public void run() {
      // code
   }
}
public class ThreadDemo {
   public static void main(String[] args) {
      MyThread t = new MyThread();
      t.start(); 
      // some code
      t.start(); 
   }
}

After starting a thread if we are trying to restart the same thread then we will get the runtime exception:-java.lang.IllegalThreadStateException. Hence we can’t start a thread more than once.

  • NEW:- Thread has been created but not yet started. The thread will be started after the start() method call.
  • RUNNABLE:- Thread is running, and currently executing.
  • TIMED_WAITING: The thread was running, and now it is waiting for a certain fixed amount of time. After that time period, the thread scheduler decides which thread will get a chance to execute.
  • WAITING: The thread was running, and now it is waiting for its turn. The thread scheduler decides which thread will get a chance to execute.
  • BLOCKED: The thread got blocked for some reason.
  • TERMINATED:- Thread execution completed.

We can get the state of a thread by using the getState() method.

MyThread mt = new MyThread();
System.out.println(mt.getState()); // NEW
mt.start();
System.out.println(mt.getState()); // RUNNABLE

We find whether a given thread is a live thread or not by using isAlive() method.

class MyThread extends Thread {}
public class Test {
   public static void main(String[] args) throws Exception {
      MyThread mt = new MyThread();
      System.out.println(mt.isAlive()); // false
      mt.start();
      System.out.println(mt.isAlive()); // true
      Thread.sleep(2000);
      System.out.println(mt.isAlive()); // false
   }
}

Using getName() method. The getName() method defined in the Thread class returns the name of the thread.

MyThread mt = new MyThread();
System.out.println(mt.getName()); // Thread-0

Using setName() method. The setName(String str) method defined in the Thread class sets the name of the given thread.

MyThread mt1 = new MyThread();
mt1.setName("KnowProgram");
System.out.println(mt1.getName()); // KnowProgram

The thread name can be changed even after starting the thread, JVM won’t give any error. The main thread is already started in the below program (question 16), but we are changing its name by getting its reference.

Thread.currentThread() method. The currentThread() method of the Thread class is a static method that returns a reference to the currently running thread.

public class Test {
   public static void main(String[] args) {
      
      Thread t = Thread.currentThread();
      System.out.println(t.getName()); // main
      
      t.setName("KnowProgram");
      System.out.println(t.getName()); // KnowProgram
   }
}

By default, JVM assigns the name of the child thread as thread-0, thread-1, thread-2, and so on.

Yes, we can create multiple threads with the same name.

MyThread mt = new MyThread();
mt.setName("KP");
mt.start();
System.out.println(mt.getName()); // KP

MyThread mt1 = new MyThread();
mt1.setName("KP");
mt1.start();
System.out.println(mt.getName()); // KP

Thread Priority, yield(), join() & sleep() Questions

Thread Priority

Every thread in JVM is created with a priority value which is called thread priority.

The default priority of the new thread is inherited from the parent thread. For example:- the default value of the main thread is 5, if a new thread is created by the main thread then the newly created thread having priority = 5.

Most of the threads are created by the main thread. The default priority of the main thread is 5, therefore by default the priority of the child thread will be 5.

The valid range of thread priority is 1 to 10.

Using getPriority() and getPriority(int p) methods. While calling the getPriority() method the passed value must belong to 1 to 10.

We will get Runtime error:- java.lang.IllegalArgumentException

The thread having high priority will get a chance first to execute.

If two threads are having the same priority then their execution order will be decided by the thread scheduler, we can’t expect anything.

Assign higher priority compared to parent and sibling threads. The thread having high priority will get the first chance to execute.

Setting the priority of a thread after it has started will immediately change the thread’s priority. The Java Thread class provides the setPriority method to adjust a thread’s priority at any point during its lifecycle.


public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Test.main() - start");

        Thread t = new Thread(() -> {
            System.out.println("Child thread");
            Thread currentThread = Thread.currentThread();
            System.out.println("Priority: " + currentThread.getPriority());
        });

        t.start();
        t.setPriority(7);

        System.out.println("Test.main() - end");
    }
}

Preventing thread from Execution (yield(), join() and sleep() Methods)

Different ways to prevent or stop a thread execution temporarily (not permanently) are:-

  1. sleep() method
  2. join() method
  3. yield() method

If a thread doesn’t want to perform any operation for a particular amount of time then we can use the sleep() method.


public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Test.main() - start");

        Thread t = new Thread(() -> {
            System.out.println("Child thread - start");
            try {
                Thread.sleep(1000); // 1 sec
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Child thread - end");
        });
        t.start();

        Thread.sleep(2000); // 2 sec
        System.out.println("Test.main() - end");
    }
}

Output:-

If a thread wants to wait until the completion of some other thread then we should go for the join() method. It is also used for timed waiting.


public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Test.main() - start");

        Thread t = new Thread(() -> {
            System.out.println("Child thread - start");
            try {
                Thread.sleep(2000); // 2 sec
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Child thread - end");
        });
        t.start();

        // main thread will wait for the completion of thread t
        t.join();
        System.out.println("Test.main() - end");
    }
}

Output:-

The yield() method pauses the currently executing thread to give the chance for waiting threads of the same priority or higher priority

  • If there are no waiting threads or all waiting threads have low priority then the same thread can continue its execution.
  • If multiple threads are waiting with the same priority then which waiting thread will get a chance:- we can’t expect, it depends on the thread scheduler.
  • The thread which is yielding, when it will get the chance back it also depends on the thread scheduler and we can’t expect exactly. 

The yield() method hints to the thread scheduler that the current thread is willing to pause its execution to let other threads of equal or higher priority have a go. However, it’s just a suggestion to the scheduler, which can decide based on its own rules and logic. Threads with lower priority won’t get a chance to run because of a yield() call. It’s like a courteous way of saying, “I can wait if there’s someone else ready to go.”


public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Test.main() - start");

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread 1 - iteration " + i);
                Thread.yield(); // give chance for thread-2 execution
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread 2 - iteration " + i);
                Thread.yield(); // give chance for thread-1 execution
            }
        });

        t1.start();
        t2.start();

        // main thread will wait till the completion of t1 & t2 threads
        t1.join();
        t2.join();
        
        System.out.println("Test.main() - end");
    }
}

Sample Output:-

By using the interrupt() method a thread can interrupt another thread. The thread will be interrupted only if it enters into the waiting or sleeping state, otherwise there will not be any impact of interrupt() method call.


public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Test.main() - start");

        Thread t = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println("Thread 1 - iteration " + i);
                    Thread.sleep(1000); // 1 sec
                }
            } catch (InterruptedException e) {
                System.out.println("Thread was interrupted.");
            } 
        });

        t.start();
        
        Thread.sleep(2500);

        // interrupt
        t.interrupt();

        System.out.println("Test.main() - end");
    }
}

Sample Output:-

When we call the interrupt() method and the target thread is not in a waiting or sleeping state then there will be no impact of interrupt call immediately. The interrupt calls will wait till the target thread enters into the waiting or sleeping state. 

  1. If the target thread enters into the waiting or sleeping state:- Then the interrupt call will immediately interrupt the target thread, and InterruptedException will be raised. Interrupt calls will not be wasted. 
  2. If the target thread never entered into the waiting or sleeping state in its lifetime:- Then the interrupt call will be wasted and no impact on the interrupt() call.
public class Test {
   public static void main(String[] args) {
      MyThread t = new MyThread();
      t.start();
      t.interrupt();
      // other work
   }
}

No, the main thread will not wait, it will continue its execution. Interrupting the thread is the task of JVM. This job will be done by JVM, the main thread will not wait, it will continue its operation.

If a thread wants to pause its execution to give the chance for remaining threads of the same priority or higher priority then the yield() method is used. But if a thread wants to wait until the completion of some other thread then the join() method is used. 

  • public static native void yield()
  • public final void join() throws InterruptedException
  • public final void join(long ms) throws InterruptedException
  • public final void join(long ms, int ns) throws InterruptedException

The yield() method doesn’t contain any overload form, doesn’t throw Exception whereas the join() method contains three overloaded forms, and all of them throw InterruptedException. The yield() method is native, and static whereas join() method is final, instance nature defined in Java (not native).

The yield() method is used If a thread wants to pause its execution to give the chance for remaining threads of the same priority or higher priority. Whereas the sleep() method is used If a thread doesn’t want to perform any operation for a particular amount of time.

  • public static native void yield()
  • public static native void sleep(long ms) throws InterruptedException
  • public static void sleep(long ms, int ns) throws InterruptedException
  • If the join() method is called on the child thread by the main thread then the main thread will wait till execution completion of the child thread. 
  • But if the join(1000) method is called on the child thread by the main thread then the main thread will wait a maximum 1000 milliseconds. If within 1000 ms child thread execution is not completed then the main thread will come out of the waiting state and try to start its own execution. 
  • When sleep(1000) is called then the compulsory thread has to wait 1000 ms after that it will try to start its own execution.
Propertiesyield()join()sleep()
Purpose (When should we use it?)We should use the yield() method if a thread wants to pause its execution to give the chance for remaining threads of the same priority or higher priority.If a thread wants to wait until the completion of some other thread then we should go for the join() method.We should use the sleep() method If a thread doesn’t want to perform any operation for a particular amount of time.
Is it overloaded?NoYesYes
Is it final?NoYesNo
Is it throwing an InterruptedException?NoYesYes
Is it Native?YesNosleep(long ms) => native;

sleep(long ms, int ns) => non-native
Is it static?YesNoYes
  • public static native void yield()
  • public final void join() throws InterruptedException
  • public final void join(long ms) throws InterruptedException
  • public final void join(long ms, int ns) throws InterruptedException
  • public static native void sleep(long ms) throws InterruptedException
  • public static void sleep(long ms, int ns) throws InterruptedException

Java Synchronization Interview Question

If multiple threads are operating simultaneously on the same Java object/class then there will be a chance to get data inconsistency problems. This problem is also known as the “race condition”. To overcome this problem we should use the synchronization concept.

Threads are executing concurrently. In this case, when the first thread execution is paused, the second thread modifies object data in the middle of the first thread execution, after resuming the first thread it uses the second thread’s modified values, but not its actual values. This is called a data inconsistent modification problem and it leads to wrong results.

Bank applications, every web application, ticket booking system, and e.t.c.

In Java synchronization is implemented by using the synchronized keyword. We can apply the synchronized keyword either to methods and/or to local blocks. So, in Java synchronization is developed:-

  1. By using synchronized methods.
  2. By using synchronized blocks.

The synchronized is a modifier applicable for methods and blocks but not for class, objects, and variables.

We can resolve data inconsistency problems.

If a method/block is declared as synchronized then at a time only one thread is allowed to execute that method/block on the given object, other threads have to wait till the completion of the allowed thread. Therefore, the data inconsistency problem will be solved.

It increases the waiting time of threads and gives performance issues. Threads will be executed one by one which increases the waiting time of the thread.

To solve these problems instead of using the synchronized keyword we can use java.util.concurrent package.

If multiple threads are operating simultaneously on the same Java object then there will be a chance of data inconsistency problems. This data inconsistency problem in Java is also known as a race condition. To resolve race conditions we should use a synchronized keyword/modifier.

Every object in Java has a unique lock that is nothing but an object-level lock. Whenever a thread wants to execute an instance synchronized method or block then it needs to acquire the object level lock.

public synchronized void m1() {
   // code
}

Every class in Java has a unique lock which is nothing but a class-level lock. Whenever a thread wants to execute a static synchronized block or method then the thread needs to acquire the class-level lock.

public static synchronized void m2() {
   // code
}

If a thread wants to execute a static synchronized method/block then the class-level lock is required. Whereas if a thread wants to execute an instance synchronized method/block then object level lock is required.

When a synchronized method is called, the object will be locked while executing the method.

Locking an object means marking the current object is not allowed to another thread for calling synchronized method/block. Unlocking an object means marking this object as available to other threads to access its functions.

Monitor means a thread that holds the lock of an object is called monitor. Note that an object has only one monitor which means an object can be locked by only one thread at a time.

No, once an object is locked by a thread then other threads can’t execute any synchronized method/block simultaneously on that object.

Yes, because the lock/unlock concept is applicable only for the synchronized method/block but not for the non-synchronized method.

Since objects are different therefore the synchronization concept is not applicable. Hence execution will be concurrent i.e. they will execute simultaneously not one by one.

If we declare an instance method as synchronized its current object is locked so that in this method instance variables of this object are modified sequentially by multiple threads. If we declare a static method as synchronized then its class’s java.lang.Object is locked so that in this method static variables are modified sequentially by multiple threads.

In a static method by using a synchronized block, we can only lock an argument object but not the current object because “this” keyword doesn’t exist in the static method.

A block surrounded by a synchronized keyword is called a synchronized block.

class Display {

   public void wish(String name) {

      // synchronized block
      synchronized(this) {
         // code
      }
   }
}

Assume we have an m1() method with 10k lines of code (linking method, internally method is calling other methods) among these lines only 10 lines require synchronization. In that case, if we declare the m1() method as the synchronized method then it is the worst kind of practice in Java programming. It will increase the waiting time of the thread and give very poor performance.

Therefore if only a few lines of code are required for synchronization then it is never recommended to declare the whole method as synchronized rather we should use the synchronized block for those lines of code.

If a method is declared as synchronized, that method’s complete logic is executed in sequence from multiple threads by using the same object. For different objects, it is executed concurrently from multiple threads. If we declare a block as synchronized, only the statements written inside that block are executed sequentially but not the complete method logic.

Using synchronized methods we can only lock the current object of the method. Using a synchronized block we can lock the current object, argument object of the method, or any class. 

Yes, we can define multiple synchronized blocks in a method.

Waiting time of thread decreased and performance improved.

synchronized(this)

synchronized(this) {
   // code
}

synchronized(ClassName.class)

synchronized(Employee.class) {
   // code
}

Yes, a thread can acquire multiple locks simultaneously.

class X {

   // x.m1() called
   public synchronized void m1() { // "x" locked

      Y y = new Y();
      synchronized(y) { // "y" locked

         Z z = new Z();
         synchronized(z) { // "z" locked

           // currently thread holds 
           // "x", "y", and "z" objects lock
         }
      }
   }
}

We want to call the m1() method. To call the m1() method,

X x = new X();
x.m1();

When a thread wants to execute the synchronized method, then the compulsory thread has to acquire the lock of the current object. Therefore to execute the m1() method thread has to acquire the “x” object lock.

To enter into the 1st synchronized block “y” object lock is required. Similarly, to enter into the 2nd synchronized block “z” object lock is required. Inside the 2nd synchronized block thread contains the lock of “x”, “y”, and “z” objects.

At a time, only one thread can get the lock of an object.

In the below two cases, we develop multiple synchronized blocks,

  1. For executing parts of method logic sequentially for doing object modification.
  2. For executing some part of method logic by locking current object and other part of method by locking argument object.

As per Java specification, there is no such type of terminology but the interview person uses this synchronized statement. According to them:- the statement present in a synchronized method or synchronized block is called a synchronized statement.

If an object is not accessible for multiple threads concurrently for modifying values, or one thread modification on the object is not affected to another thread, then that object is called a thread-safe object.

If an object is accessible for multiple threads concurrently for modifying values, and modification by one thread on this object is also available to other threads then that object is called a non-thread-safe object.

We can develop thread-safe objects in two ways,

  • By declaring all mutator methods of this object as synchronized. Or,
  • By creating objects as immutable.

Inter-Thread Communication, Deadlock, Daemon Thread Interview Questions

Inter-Thread Communication

The process of executing multiple threads alternatively in sequence with communication for modifying and reading data from the same object is called inter-thread communication. We develop this concept when two different dependent tasks need to be executed continuously in sequence by two different threads on the same object.

Two threads can communicate with each other by using wait(), notify(), and notifyAll() methods.

These methods are given in java.lang.Object class.

Thread can call wait(), notify(), and notifyAll() methods on any Java object. To call a method on any object the method must be present in that class or parent class. Since java.lang.Object is the superclass of all Java classes therefore they are defined in the java.lang.Object class.

We can call these methods only from the synchronized area (block/method) else IllegalMonitorStateException will be raised at runtime.


public class Test {
    private static final Object LOCK = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (LOCK) {
                try {
                    System.out.println("Thread 1: waiting for the lock.");
                    LOCK.wait();
                    System.out.println("Thread 1: Notified and resumed execution.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (LOCK) {
                try {
                    Thread.sleep(2000);
                    System.out.println("Thread 2: Work done. Notifying thread 1.");
                    LOCK.notify(); // Thread 2 notifies thread 1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();
    }
}

When a thread calls the wait() method on an object then immediately thread releases the lock of that particular object and enters into the waiting state. 

Note that only the lock of that particular object is released, it won’t release the lock of other objects. Suppose at the time of calling wait() method, thread contains the lock of 10 objects then the thread will release lock for only 1 object on which wait() method was called, it won’t release lock for the remaining 9 objects.

When a thread calls notify() method on any object then it releases the lock of the object but may not immediately. And it also gives notification to the waiting thread.

For example, the x.wait() method was called by one thread. This thread will release the lock and went into the waiting state to get the notification.

After calculation, x.notify() was called by another thread. Therefore this thread will release the lock of the object and give notification to the waiting thread.

That thread will be in the waiting state forever.

The wait(), notify(), and notifyAll() are only methods where the thread releases the lock.

We can use notify() method to give the notification for only one waiting thread. If multiple threads are waiting to get the notification then only one thread will be notified, the remaining threads have to wait for further notifications. We can’t expect which thread will be notified, it completely depends upon the JVM and Thread Scheduler. We can use notifyAll() to give the notification for all waiting threads on a particular object.

InterruptedException

  • The sleep(100) blocks thread execution independent of other threads for 100 milliseconds.
  • The join(100) blocks thread execution by depending on whether another thread execution is completed or till 100 milliseconds completed, whichever comes first thread resumes its execution immediately.
  • The wait(100) blocks thread execution by depending on either other thread till it is called notify() or till 100 milliseconds are completed, whichever comes first thread resumes its execution immediately.
  1. The wait() method throws an InterruptedException, which is a checked exception. Therefore it must be handled whenever wait() is called.
  2. The wait(), notify(), and notifyAll() method must be called only in synchronized method/block else we will get an IllegalMonitorStateException.
  3. If we call the no-arg wait() method, later from another thread then we must call notify()/notifyAll() method else this thread execution is in WAITING state forever.
  4. If we want to resume thread after some time even though notify() method is not called we must use wait(long time) or wait(long ms, int ns) method.

Deadlock and Starvation

If two threads are waiting for each other to complete their execution then such type of infinite waiting situation is called deadlock. See more:- Deadlock in Java with example.

The synchronized keyword. Deadlock occurs only because of the synchronized keyword.

If we don’t use the synchronized keyword then we never encounter the deadlock situation. In some cases, the program may reach into the infinite waiting state but it is not a deadlock situation. See more:- How to detect deadlock in Java.

We can avoid deadlock situation by using follows:-

  • Avoid nested locks:- Avoid nested locks as much as possible.
  • Avoid unnecessary locks:- Use locks only on necessary objects.
  • Use thread join:- Use join(long ms) or join(long ms, int ns) to wait for a certain period of time.
  • Use java.util.concurrent package.

We discussed this program in detail here:- Deadlock in Java

Long time waiting of two threads for each other where waiting never ends is called deadlock whereas the long time waiting of a thread where waiting may end at a certain point is called starvation. See more:- Starvation in Java

A thread often acts in response to the action of another thread. If the other thread’s action is also a response to the action of another thread, then livelock may result. As with deadlock, livelocked threads are unable to make further progress.

Daemon Thread

We can create two types of threads:- non-daemon thread and daemon thread.

A thread that is running in the background based on a schedule to provide services to non-daemon threads is called a daemon thread. Daemon threads are also called service threads. Examples of daemon threads in Java are:- garbage collectors, attach listeners, signal dispatchers, and e.t.c. See more:- Daemon Thread in Java

A thread that executes the main logic of the program/project is called a non-daemon thread. Example of non-daemon thread:- main thread.

The daemon/non-daemon nature of thread is inherited from the parent thread. If the parent thread is a daemon thread then the child thread also will be a daemon thread and similarly, if the parent thread is a non-daemon thread then the child thread will be a non-daemon thread.

Most of the threads are created by the main thread. Since the main thread is a non-daemon thread, therefore, all the child threads created by the main method are also non-daemon threads.

We can check the daemon nature of the thread by using the isDaemon() method of the Thread class.

We can change the daemon nature of the thread by using the setDaemon() method of the Thread class.

The setDaemon() method must be called before starting of the thread else we will get java.lang.IllegalThreadStateException.

No, because the setDaemon() method must be called before starting the thread.

No, there is no way to change the daemon nature of the main thread because the main thread is already started by the JVM at the very beginning. Therefore it is impossible to change the daemon nature of the main thread.

The main purpose of the daemon thread is to provide services to the non-daemon threads. If there is no non-daemon thread left in the program then daemon threads will be terminated.

No, once the last non-daemon thread in the program terminates then all daemon threads of the program will be terminated immediately. Hence, full execution of the daemon thread is not guaranteed.

JVM waits only for non-daemon threads to complete.

The thread that is managed completely by the JVM without taking underlying operating system support is called the green thread. Most of the operating systems won’t provide support for the green thread model. Very few operating systems like Sun Solaris provide support for the green thread model.

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published. Required fields are marked *