In this article I tried to answer the most common question regarding the creation of threads in Java: whether to implement Runnable interface or extend Thread class? While answering this question I also tried to cover the basic concepts involved in similar scenarios.
2 Whether to implement ‘Runnable’ interface or extend ‘Thread’ class?
In java sometimes we enjoy the liberty of implementing similar code in multiple ways. Although most of the times from a helicopter view we couldn’t figure out the subtle issues involved in each way unless we go into depth. The same holds true in this scenario as well.
In regard to the above question, I answer it in 3 parts.
1. Technical Constraint:
Although java supports multiple type inheritance, it doesn’t support multiple class inheritance. In other words, a class or interface can inherit from multiple inheritances, but a class can only inherit from one super class. Under this technical constraint, if a class extends ‘Thread’ class, it can’t extend any other class. Thus we loose the liberty of inheritance in this case. In view of this constraint, implementing ‘Runnable’ interface remain the only choice incase the class already inherit another class.
2. Semantic Difference:
Inheritance, being ‘is-a’ relationship, provides semantic definition of the class and categorize it to the category of the super class. Moreover incase of type (interface) inheritance, class or interface also provided with additional semantic ability. For e.g. when a class implements a ‘Serializable’ interface, it belongs to ‘Serializable’ category where it gets the ability to be serialized.
Thus in the above scenario, if your code semantically suitable to be categorized as a ‘runnable’ code which can run using a thread, it would be logical to implement the ‘Runnable’ interface. On the other hand, if your code is semantically like a thread in itself and can run itself as a thread, it would be semantically good to extend the ‘Thread’ class.
3. Implementation Difference:
The above two points are easy to understand and implement but at the last I would like to touch the most subtle issue involved in the above scenario.
Note that when we implement the ‘Runnable’ interface, our class is acting as a factory for creating threads. Thus all threads execute the run (and hence all non-static methods) of the same instance of the class. Thus if the run method of the class implementing ‘Runnable’ interface executes any synchronized method or block, all generated threads need to acquire lock of the same instance of the class. But incase of extending from the thread class, every new thread is a new instance of the class. Thus if run method internally executes any synchronized method or block, each thread needs to acquire lock on its own instance instead of any single instance. The explanation in this paragraph is depicted below in the figure.
To further explain this, let’s consider the code below:
public class RunnableClass implements Runnable {
public void run() {
m1();
}
public synchronized void m1() {
System.out.println("Lock acquired by [" + Thread.currentThread().getName() +"] Thread on instace: " + this);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void m() {
Thread t1 = new Thread(this, "T1");
Thread t2 = new Thread(this, "T2");
Thread t3 = new Thread(this, "T3");
Thread t4 = new Thread(this, "T4");
Thread t5 = new Thread(this, "T5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
public static void main(String[] args) {
new RunnableClass().m();
}
}
The output of this program would be similar to this:
Lock acquired by [T1] Thread on instace: com.test.general.RunnableClass@9304b1
Lock acquired by [T5] Thread on instace: com.test.general.RunnableClass@9304b1
Lock acquired by [T4] Thread on instace: com.test.general.RunnableClass@9304b1
Lock acquired by [T2] Thread on instace: com.test.general.RunnableClass@9304b1
Lock acquired by [T3] Thread on instace: com.test.general.RunnableClass@9304b1
Note that the id of the object on which lock is to be acquired by all threads is same. This evident that all threads will compete for the same lock hence if the run method internally calls synchronized method or block, the performance could be a major concern here.
Now look at the code below where ‘ThreadClass’ extends ‘Thread’ class.
public class ThreadClass extends Thread {
public ThreadClass(String name) {
super(name);
}
public void run() {
m1();
}
public synchronized void m1() {
System.out.println("Lock acquired by [" + Thread.currentThread().getName() +"] Thread on instace: " + this);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new ThreadClass("T1").start();
new ThreadClass("T2").start();
new ThreadClass("T3").start();
new ThreadClass("T4").start();
new ThreadClass("T5").start();
}
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
The output of this program would be similar to this:
Lock acquired by [T1] Thread on instace: com.test.general.ThreadClass@9304b1
Lock acquired by [T3] Thread on instace: com.test.general.ThreadClass@a90653
Lock acquired by [T5] Thread on instace: com.test.general.ThreadClass@c17164
Lock acquired by [T4] Thread on instace: com.test.general.ThreadClass@1fb8ee3
Lock acquired by [T2] Thread on instace: com.test.general.ThreadClass@61de33
Note that the id of the object on which threads acquired lock is different. This means each thread needs to acquire lock on its own instance and hence if the run method internally calls synchronized method or block, threads need not to compete with each other to execute the same.
The above point suggests that incase the thread needs to execute some synchronized method or block, ‘extending Thread class’ could be a better solution.
3 Conclusion
Threads in java can be created in 2 ways. One can select an appropriate way depending on the hierarchy of the class, semantic definition of the class and execution of non-static synchronized code.