Creating Threads by Extending the Thread Class in Java
Course: Core Java Training
Topic: Creating Threads by Extending the Thread Class
Course Link: Core Java Training Course
Demo Link: Register for a Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction
Multithreading is an important feature of Java that allows a program to perform multiple tasks concurrently. A thread is an independent path of execution within a process. Java provides the built-in Thread class to create, start, and manage threads.
One basic way to create a thread is to define a class that extends Thread and overrides its run() method. The run() method contains the statements that should execute when the thread starts.
For example, an application might use a separate thread to process a report while the main thread continues handling other work. Extending Thread provides a straightforward way to demonstrate this behavior.
2. Learning Objectives
- Understand the purpose of the Java
Thread class.
- Learn how to create a custom thread by extending
Thread.
- Override the
run() method to define a task.
- Understand how and why to call
start().
- Distinguish between calling
start() and calling run() directly.
- Learn how multiple thread objects execute concurrently.
- Understand common mistakes and best practices for thread creation.
3. What Is the Thread Class?
The Thread class belongs to the java.lang package, which is automatically available in a Java program. It represents a thread of execution and provides methods for starting a thread, checking its state, interrupting it, waiting for it to finish, and retrieving information about it.
Important methods include:
| Method |
Purpose |
start() |
Starts a new thread of execution. |
run() |
Defines the task executed by the thread. |
sleep(long millis) |
Pauses the currently executing thread for approximately the specified duration, subject to scheduling. |
join() |
Waits for another thread to terminate. |
currentThread() |
Returns the currently executing thread. |
getName() |
Returns the thread's name. |
setName(String name) |
Sets the thread's name. |
isAlive() |
Checks whether a thread has started and has not yet terminated. |
interrupt() |
Requests interruption of a thread. |
getState() |
Returns the thread's current Java state. |
4. Syntax for Creating a Thread by Extending Thread
The general syntax is:
class MyThread extends Thread {
@Override
public void run() {
// Task to execute in this thread
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
Explanation:
class MyThread extends Thread creates a subclass of the Thread class.
run() is overridden to define the task.
new MyThread() creates a thread object in the NEW state.
start() asks the JVM to start the new thread of execution.
- The new thread invokes its
run() method while the main thread can continue its own work.
5. Step-by-Step Example
Let's create a simple thread that displays a message.
class WelcomeThread extends Thread {
@Override
public void run() {
System.out.println("Welcome from the child thread!");
}
}
public class ThreadDemo {
public static void main(String[] args) {
WelcomeThread thread = new WelcomeThread();
thread.start();
System.out.println("Main thread is executing.");
}
}
Possible output:
Main thread is executing.
Welcome from the child thread!
The output order is not guaranteed. Depending on scheduling, the child thread may print its message before the main thread prints its message.
How the program works
- The JVM starts the application and executes the
main() method on the main thread.
- The statement
new WelcomeThread() creates a thread object.
- The call to
start() initiates a new thread of execution.
- The new thread executes the overridden
run() method.
- The main thread continues executing independently of the child thread.
6. Understanding the run() Method
The run() method contains the code that the thread should execute. When extending Thread, override this method to define the thread's behavior.
class MessageThread extends Thread {
@Override
public void run() {
System.out.println("Thread task started.");
System.out.println("Thread task completed.");
}
}
Overriding run() alone does not start a separate thread. The thread object must be started using start() to initiate a new execution path.
7. Difference Between start() and run()
This is one of the most important concepts when creating threads in Java.
| Feature |
start() |
run() |
| Purpose |
Starts a new thread of execution. |
Contains the task's execution logic. |
| Execution |
The JVM invokes run() on the started thread. |
A direct call runs like an ordinary method call on the current thread. |
| Concurrency |
Allows the new thread to execute concurrently with other threads. |
Does not create a new thread by itself. |
| Repeated calls |
Calling it again on the same thread object throws IllegalThreadStateException. |
Calling it directly again simply invokes the method again on the calling thread. |
Example: Calling run() directly
class DemoThread extends Thread {
@Override
public void run() {
System.out.println(
"Executing on: " + Thread.currentThread().getName()
);
}
}
public class RunMethodExample {
public static void main(String[] args) {
DemoThread thread = new DemoThread();
thread.run();
System.out.println(
"Main executes on: " + Thread.currentThread().getName()
);
}
}
Output:
Executing on: main
Main executes on: main
Since run() is called directly, both statements execute on the main thread. To start a separate thread, use thread.start().
```html
8. Creating a Thread with a Constructor
The Thread class provides constructors that allow you to assign a name to a thread. Naming threads makes it easier to identify them in logs, debugging tools, and monitoring output.
class NamedThread extends Thread {
public NamedThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("Thread name: " +
Thread.currentThread().getName());
}
}
public class NamedThreadExample {
public static void main(String[] args) {
NamedThread thread = new NamedThread("Worker-1");
thread.start();
}
}
Output:
Thread name: Worker-1
Explanation:
- The constructor receives a thread name.
super(name) passes that name to the parent Thread constructor.
start() starts the thread.
Thread.currentThread().getName() retrieves the name of the thread currently executing the code.
9. Creating Multiple Threads by Extending Thread
A Java program can create multiple objects of a class that extends Thread. Each object represents a separate thread and can execute the overridden run() method independently.
class NumberThread extends Thread {
private final String taskName;
public NumberThread(String taskName) {
super(taskName);
this.taskName = taskName;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(taskName + ": " + i);
}
}
}
public class MultipleThreadDemo {
public static void main(String[] args) {
NumberThread thread1 = new NumberThread("Thread A");
NumberThread thread2 = new NumberThread("Thread B");
thread1.start();
thread2.start();
}
}
Possible output:
Thread A: 1
Thread B: 1
Thread A: 2
Thread B: 2
Thread B: 3
Thread A: 3
Thread A: 4
Thread B: 4
Thread A: 5
Thread B: 5
The output order can differ on each execution. Each thread has its own execution path, and the scheduler determines how their work is interleaved.
10. Using sleep() in a Thread
The static method Thread.sleep() pauses the currently executing thread for a specified duration. It is commonly used in demonstrations, simulations, and tasks that need timed delays.
class CountingThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("Count: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Thread interrupted.");
return;
}
}
}
}
public class SleepExample {
public static void main(String[] args) {
CountingThread thread = new CountingThread();
thread.start();
}
}
Expected behavior: The thread prints numbers with an approximately half-second pause between iterations. Actual timing depends on the operating system and JVM scheduler.
Important points:
sleep() pauses the current thread, not every thread in the application.
- The method can throw
InterruptedException.
- Restoring the interrupt status with
Thread.currentThread().interrupt() preserves the interruption signal when the method handles the exception.
- Sleeping does not guarantee that the thread will resume at the exact end of the requested duration.
11. Waiting for a Thread with join()
The join() method allows one thread to wait until another thread finishes. It is useful when the main thread needs to wait for the result of a worker thread before continuing.
class TaskThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println("Task: " + i);
}
}
}
public class JoinExample {
public static void main(String[] args) {
TaskThread thread = new TaskThread();
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Main thread interrupted.");
return;
}
System.out.println("Task completed. Main continues.");
}
}
Output:
Task: 1
Task: 2
Task: 3
Task completed. Main continues.
The main thread waits at join() until the worker thread terminates. This ensures the completion message appears after the worker's output.
12. Checking Thread State and Activity
Java provides methods for checking whether a thread is alive and retrieving its current state. These methods are useful for understanding thread execution, although a state check is only a snapshot and can change immediately afterward.
class StateThread extends Thread {
@Override
public void run() {
System.out.println("Thread is executing.");
}
}
public class ThreadStateExample {
public static void main(String[] args) throws InterruptedException {
StateThread thread = new StateThread();
System.out.println(thread.getState());
thread.start();
thread.join();
System.out.println(thread.getState());
System.out.println(thread.isAlive());
}
}
Output:
NEW
Thread is executing.
TERMINATED
false
The initial state is NEW. After the thread completes and join() returns, its state is TERMINATED and isAlive() returns false.
13. Thread Priority
Java allows a thread to have a priority value. The standard priority range is from Thread.MIN_PRIORITY (1) to Thread.MAX_PRIORITY (10), with Thread.NORM_PRIORITY (5) as the default.
class PriorityThread extends Thread {
public PriorityThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(
getName() + " priority: " + getPriority()
);
}
}
public class PriorityExample {
public static void main(String[] args) {
PriorityThread low = new PriorityThread("Low");
PriorityThread high = new PriorityThread("High");
low.setPriority(Thread.MIN_PRIORITY);
high.setPriority(Thread.MAX_PRIORITY);
low.start();
high.start();
}
}
Important: Thread priority is a scheduling hint, not a guarantee of execution order. Do not use priorities to enforce correctness or to ensure that one thread always completes before another.
14. Handling Thread Interruption
Interruption is a cooperative mechanism for requesting that a thread stop what it is doing or respond to a cancellation request. It is not the same as forcibly terminating a thread.
class InterruptibleThread extends Thread {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Working...");
Thread.sleep(300);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Thread is stopping.");
}
}
public class InterruptExample {
public static void main(String[] args) throws InterruptedException {
InterruptibleThread thread = new InterruptibleThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
thread.join();
System.out.println("Main method finished.");
}
}
Explanation:
- The worker performs its task until interruption is requested.
interrupt() sets the interrupt status and causes certain blocking methods, including sleep(), to throw InterruptedException.
- The example restores the interrupt status when catching the exception.
- The worker then finishes, allowing
join() to return.
Applications should design interruption handling according to the task's cancellation requirements. Avoid deprecated thread termination methods such as stop().
15. Limitations of Extending the Thread Class
Although extending Thread is useful for learning and small examples, it is not always the best design for larger applications.
- Single class inheritance: A class that extends
Thread cannot extend another class.
- Task and execution are coupled: The business logic is combined with the thread object.
- Limited reuse: The task is less flexible when it must be executed by an executor or another execution mechanism.
- Resource management: Creating many thread objects manually can lead to unnecessary resource usage.
For many real applications, implementing Runnable or Callable and using an ExecutorService provides better separation between the task and the mechanism that executes it.
16. Thread Class vs. Runnable Interface
| Feature |
Extending Thread |
Implementing Runnable |
| Approach |
Subclass the Thread class. |
Implement the Runnable interface. |
| Inheritance |
Uses the class's single inheritance opportunity. |
Allows the class to extend another class if needed. |
| Task separation |
Task logic is placed in the thread subclass. |
Task logic is separated from the thread object. |
| Executor compatibility |
Possible, but the task and thread are more tightly coupled. |
Works naturally with executor-based APIs. |
| Best suited for |
Basic demonstrations and specialized thread subclasses. |
Reusable tasks and many practical application designs. |
17. Common Mistakes When Extending Thread
| Mistake |
Problem |
Correct Practice |
Calling run() directly |
Executes the method on the current thread rather than starting a new thread. |
Call start() to initiate a separate thread. |
Calling start() twice on the same object |
Throws IllegalThreadStateException. |
Create a new thread object for a new execution. |
| Assuming a fixed output order |
Thread scheduling is not guaranteed to follow source-code order across threads. |
Use synchronization or coordination such as join() when order matters. |
Ignoring InterruptedException |
Can cause cancellation requests to be lost. |
Handle interruption appropriately and restore the interrupt status when necessary. |
| Sharing mutable data without coordination |
May cause race conditions or inconsistent results. |
Use synchronization, locks, atomic variables, or other concurrency utilities. |
| Creating excessive threads |
Can increase memory usage and scheduling overhead. |
Consider thread pools and the Executor framework. |
18. Best Practices
- Override only the methods needed for the intended behavior, usually
run() for a basic example.
- Call
start() instead of directly calling run() when a new thread is required.
- Give threads meaningful names to simplify debugging.
- Use
join() or other coordination mechanisms when task completion must be awaited.
- Respond to interruption rather than ignoring it.
- Avoid relying on thread priorities or timing delays to guarantee ordering.
- Protect shared mutable state against concurrent access.
- Prefer
Runnable and executor-based designs for reusable application tasks unless extending Thread serves a specific purpose.
19. Practice Exercises
- Create a class named
GreetingThread that extends Thread and prints a greeting message.
- Create a thread named
CounterThread that prints numbers from 1 to 10.
- Use a constructor and
super(name) to assign a custom name to a thread.
- Create two thread objects that print different messages and observe the output order.
- Use
Thread.sleep() to add a delay between printed values.
- Use
join() to make the main thread wait for a worker thread.
- Check a thread's state before starting it and after it finishes.
- Implement interruption handling so a running thread can stop cooperatively.
20. Mini Project: Multithreaded Number Printer
Objective: Create a simple application that starts two named threads, each printing a sequence of numbers.
Requirements:
- Create a class named
NumberPrinter that extends Thread.
- Use a constructor to accept a thread name and a maximum number.
- Override
run() to print the numbers from 1 to the maximum.
- Start two different instances.
- Use
join() so the main thread waits for both threads to finish.
class NumberPrinter extends Thread {
private final int maxNumber;
public NumberPrinter(String name, int maxNumber) {
super(name);
this.maxNumber = maxNumber;
}
@Override
public void run() {
for (int i = 1; i <= maxNumber; i++) {
System.out.println(getName() + " - " + i);
}
}
}
public class NumberPrinterProject {
public static void main(String[] args) {
NumberPrinter first = new NumberPrinter("Printer-A", 5);
NumberPrinter second = new NumberPrinter("Printer-B", 5);
first.start();
second.start();
try {
first.join();
second.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Main thread interrupted.");
return;
}
System.out.println("Both threads completed.");
}
}
Expected behavior: Both threads print numbers from 1 to 5. Their output may be interleaved, but the final completion message appears only after both threads have terminated.
21. Interview Questions and Answers
Q1. How do you create a thread by extending the Thread class?
Create a subclass of Thread, override its run() method, instantiate the subclass, and call start().
Q2. Why do we override run()?
The run() method defines the task that should execute when the thread starts.
Q3. What happens when start() is called?
The JVM starts a new thread of execution and invokes that thread's run() method.
Q4. Can we start the same thread object twice?
No. Calling start() again on a thread that has already been started throws IllegalThreadStateException, even if the thread has finished.
Q5. What happens when run() is called directly?
It executes like a normal method call on the current thread and does not start a new thread by itself.
Q6. Can a class that extends Thread extend another class as well?
No. Java supports extending only one class. The class can still implement interfaces.
Q7. Why is implementing Runnable often preferred?
It separates task logic from thread management and allows the class to extend another class. It also integrates naturally with executor-based task management.
Q8. What is the purpose of join()?
join() makes the calling thread wait for another thread to terminate.
Q9. What is the purpose of Thread.sleep()?
It pauses the currently executing thread for a specified duration, subject to scheduling and timing precision.
Q10. What is the role of super(name) in a Thread subclass?
It invokes the parent class constructor that assigns a name to the thread.
22. Quick Revision Checklist
Thread is available in the java.lang package.
- Create a subclass using
extends Thread.
- Override
public void run() to define the task.
- Create an instance of the subclass.
- Call
start() to start a new thread of execution.
- Calling
run() directly does not start a new thread.
- Use
super(name) to assign a custom thread name.
- Use
sleep() for timed pauses and join() to wait for completion.
- Handle interruptions and shared data carefully.
- Consider
Runnable and executors for reusable, production-oriented tasks.
23. Conclusion
Creating threads by extending the Thread class is a fundamental technique for learning Java multithreading. By defining a subclass, overriding run(), and invoking start(), developers can execute tasks on separate threads. Understanding thread naming, lifecycle states, interruption, and coordination methods builds a strong foundation for advanced concurrency concepts.
Although extending Thread is useful for basic examples, separating task logic with Runnable and managing execution through the Executor framework is often more flexible for real-world applications.
Continue learning Core Java: Explore the Core Java Training course, register for a course demo, or visit the JustAcademy website.