Java supports multi-threaded program. A thread is a light-weight process within a process. Normally we don’t create threads by ourselves. However, it is used intensively in different frameworks. Modern CPUs have multiple cores, which allow the device running them to execute multiple threads simultaneously, each core runs its own thread or process, enable parallel execution.

Thread states

In Java, a thread can exist in one of the following states as defined in the java.lang.Thread.State enum. These states represent the life cycle of a thread from creation to termination.

  • NEW
    • A thread is in the NEW state when it has been created but has not yet started.
    • This happens when you instantiate a Thread object but have not called its start() method.
  • RUNNABLE
    • A thread is in the RUNNABLE state when it is ready to run but is waiting for CPU time to execute.
    • It may not be running immediately because the thread/OS scheduler decides when to run it.
    • This happens after the start() method is called.
  • BLOCKED
    • A thread is in the BLOCKED state when it is waiting for a monitor lock to enter a synchronised block or method.
    • It transitions to RUNNABLE when the lock becomes available.
  • WAITING
    • A thread is in the WAITING state when it is waiting indefinitely for another thread to perform a specific action (e.g., notify it).
    • This happens when methods like Object.wait() or Thread.join() without a timeout are called.
  • TIMED_WAITING
    • A thread is in the TIMED_WAITING state when it is waiting for another thread for a specific amount of time.
    • This can happen when methods like Thread.sleep(), Object.wait(timeout), or Thread.join(timeout) are called.
  • TERMINATED
    • A thread is in the TERMINATED state when it has finished execution, either by completing its task or due to an exception.

Creating a thread

Threads can be created by using two mechanisms : 

  1. Extending the Thread class 
  2. Implementing the Runnable Interface

Extending the Thread class

We create classes A and B that extends the java.lang.Thread class, they will later run in multithreading. Both classes override the run() method available in the Thread class. A thread will execute the run() method when it begins its life. We create objects of our new classes and call start() method to start the execution of a threads. start() invokes the run() method on the Thread objects.

class A extends Thread {
	@Override
	public void run() {
		for (int i=0; i<5; i++){
			System.out.println("hi");
		}
	}
}
 
class B extends Thread {
	@Override
	public void run() {
		for (int i=0; i<5; i++) {
			System.out.println("hello");
		}
	}
}
 
public class Demo {
	public static void main(String[] args) {
		A obj1 = new A();
		B obj2 = new B();
		
		obj1.start(); // Will execute the run method
		obj2.start();
	}
}

Now both obj1 and obj2 are running concurrently. However, the result printed seems as if they are running sequentially, with all hi messages followed by all hello messages. In reality, both threads are running concurrently in the background. However, the Operating System’s thread scheduler determines when each thread gets CPU time. In this case, the scheduler has given obj1 (printing hi) enough continuous CPU time to finish printing all its messages before switching to the other thread (printing hello).

This results in the output appearing as though the threads are running sequentially. The process execution is so fast that no visible interleaving of hi and hello occurs during their allocated time slices.

hi
hi
hi
hi
hi
hello
hello
hello
hello
hello

To visualise the parallel execution, you can try to increase the number of loops.

//...
	public void run {
		for (int i=0; i<100; i++){ // Loop 100 times
			System.out.println("hi");
		}
	}
//...
	public void run {
		for (int i=0; i<100; i++){  // Same here
			System.out.println("hello");
		}
	}
hi
hi
hi
hi
hi
hello
hello
hello
hello
hello
hello
hello
hello
hi
hi
hi
hi
hello
hello
...

Now we give more tasks for a thread to run so that they can’t finish the entire job in one allocated time slice, and demonstrates the parallel execution. The OS thread scheduler determines how CPU time is divided between the two threads. This division may cause the threads to switch back and forth multiple times during execution. Since the threads have more work to do (100 iterations), the scheduler has more opportunities to switch between them, resulting in interleaved output.

Implementing the Runnable interface

The Runnable interface has a run() method which work for threads.

class A implements Runnable {
	public void run() {
		for (int i=0; i<5; i++){
			System.out.println("hi");
		}
	}
}
 
class B implements Runnable {
	public void run() {
		for (int i=0; i<5; i++) {
			System.out.println("hello");
		}
	}
}
 
public class Demo {
	public static void main(String[] args) {
		Runnable obj1 = new A();
		Runnable obj2 = new B();
		
		Thread t1 = new Thread(obj1); // It takes a Runnable object
		Thread t2 = new Thread(obj2);
		
		t1.start(); // Will execute the run method
		t2.start();
	}
}

Work with lambda expression

Since the Runnable is a functional interface, it works with Lambda Expressions. We first rewrite them using anonymous inner class.

public class Demo {
	public static void main(String[] args) {
		Runnable obj1 = new Runnable {
			public void run() {
				for (int i=0; i<5; i++){
					System.out.println("hi");
				}
			}
		};
		Runnable obj2 = new Runnable {
			public void run() {
				for (int i=0; i<5; i++){
					System.out.println("hello");
				}
			}
		};
		Thread t1 = new Thread(obj1); 
		Thread t2 = new Thread(obj2);
		
		t1.start(); 
		t2.start();
	}
}

Now, we can simply the entire code like below with lambda.

public class Demo {
	public static void main(String[] args) {
		Runnable obj1 = () -> {
			for (int i=0; i<5; i++){
				System.out.println("hi");
			}
		};
		Runnable obj2 = () -> {
			for (int i=0; i<5; i++){
				System.out.println("hello");
			}
		};
		Thread t1 = new Thread(obj1); 
		Thread t2 = new Thread(obj2);
		
		t1.start(); 
		t2.start();
	}
}

Back to parent page: Java

Java Multithreading Runnable Lambda_Expression