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 itsstart()
method.
- A thread is in the
- 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.
- A thread is in the
- 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.
- A thread is in the
- 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()
orThread.join()
without a timeout are called.
- A thread is in the
- 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)
, orThread.join(timeout)
are called.
- A thread is in the
- TERMINATED
- A thread is in the
TERMINATED
state when it has finished execution, either by completing its task or due to an exception.
- A thread is in the
Creating a thread
Threads can be created by using two mechanisms :
- Extending the Thread class
- 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