Question 4
Create two threads using the Thread class and Runnable interface. Explain the differences and demonstrate thread creation and management.
/**
* 1. A class that creates a thread by extending the Thread class.
*/
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread 1 (extending Thread) is starting...");
for (int i = 1; i <= 5; i++) {
System.out.println("Thread 1: " + i);
try {
// Pause for half a second to simulate work
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Thread 1 was interrupted.");
}
}
System.out.println("Thread 1 has finished.");
}
}
/**
* 2. A class that creates a thread by implementing the Runnable interface.
*/
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread 2 (implementing Runnable) is starting...");
for (int i = 1; i <= 5; i++) {
System.out.println("Thread 2: " + i);
try {
// Pause for half a second
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Thread 2 was interrupted.");
}
}
System.out.println("Thread 2 has finished.");
}
}
/**
* The main class to demonstrate thread creation and management.
*/
public class ThreadDemo {
public static void main(String[] args) {
System.out.println("Main thread is starting.");
// --- Create and start the first thread (extending Thread) ---
MyThread thread1 = new MyThread();
thread1.start(); // This calls the run() method in a new thread
// --- Create and start the second thread (implementing Runnable) ---
MyRunnable runnableTask = new MyRunnable();
Thread thread2 = new Thread(runnableTask); // Pass the task to a Thread
thread2.start(); // This also calls run() in a new thread
// --- Thread Management: Wait for threads to finish ---
try {
// The main thread will pause here and wait for thread1 and thread2 to complete.
thread1.join();
thread2.join();
} catch (InterruptedException e) {
System.out.println("Main thread was interrupted.");
}
System.out.println("Main thread has finished.");
}
}
How to Compile and Run
- Save: Save the code in a file named
ThreadDemo.java
. - Compile: Open your terminal or command prompt and run the compiler:
javac ThreadDemo.java
- Run: After compiling, run the program:
java ThreadDemo
Expected Output
Because the threads run concurrently, the exact order of the output from "Thread 1" and "Thread 2" may vary slightly each time you run it. However, the structure will be similar to this:
Main thread is starting.
Thread 1 (extending Thread) is starting...
Thread 2 (implementing Runnable) is starting...
Thread 1: 1
Thread 2: 1
Thread 2: 2
Thread 1: 2
Thread 1: 3
Thread 2: 3
Thread 2: 4
Thread 1: 4
Thread 1: 5
Thread 2: 5
Thread 2 has finished.
Thread 1 has finished.
Main thread has finished.
Program Explanation
This program creates and starts two separate threads that run concurrently with the main thread. Each thread simply counts from 1 to 5, printing a message at each step.
Method 1: Extending the
Thread
Class (MyThread
)- A class
MyThread
is created that inherits from thejava.lang.Thread
class. - It must override the
run()
method. The code insiderun()
is what the new thread will execute. - To start the thread, you create an instance of
MyThread
and call itsstart()
method.
- A class
Method 2: Implementing the
Runnable
Interface (MyRunnable
)- A class
MyRunnable
is created that implements thejava.lang.Runnable
interface. - This also requires implementing the
run()
method, which contains the code for the thread to execute. - To start the thread, you first create an instance of
MyRunnable
. Then, you pass this instance into the constructor of a newThread
object. Finally, you call thestart()
method on thatThread
object.
- A class
Key Differences and Best Practice
Feature | Extending Thread | Implementing Runnable |
---|---|---|
Inheritance | Your class cannot extend any other class because Java does not support multiple inheritance. This is a major limitation. | Your class can still extend another class. This provides much more flexibility. |
Design | Mixes the "thread" (the worker) with the "task" (the code to run). | Separates the task (Runnable ) from the worker (Thread ). This is a better object-oriented design. |
Recommendation | Generally avoided unless you need to modify the Thread class's fundamental behavior. | This is the preferred method. It is more flexible and promotes better code design. |
1. Creating Threads
a) Using the Thread
class (by extending it):
// MyThread.java
class MyThread extends Thread {
private String threadName;
private int count;
public MyThread(String name, int count) {
this.threadName = name;
this.count = count;
System.out.println("Creating " + threadName );
}
@Override
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 0; i < count; i++) {
System.out.println("Thread: "
+ threadName + ", Count: " + i);
// Using sleep to similate work
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread "
+ threadName + " interrupted.");
}
System.out.println("Thread "
+ threadName + " exiting.");
}
}
b) Using the Runnable
interface (by implementing it):
// MyRunnable.java
class MyRunnable implements Runnable {
private String runnableName;
private int count;
private Thread thread;
public MyRunnable(String name, int count) {
this.runnableName = name;
this.count = count;
System.out.println("Creating Runnable: "
+ runnableName );
}
@Override
public void run() {
System.out.println("Running task for: "
+ runnableName );
try {
for(int i = 0; i < count; i++) {
// Thread.currentThread().getName() gives the actual thread's name
System.out.println("Runnable Task: "
+ runnableName + " on Thread: "
+ Thread.currentThread().getName()
+ ", Count: " + i);
Thread.sleep(70);
// Slightly different sleep time
}
} catch (InterruptedException e) {
System.out.println("Runnable task "
+ runnableName + " interrupted.");
}
System.out.println("Runnable task "
+ runnableName + " finishing.");
}
// Optional method to create and start the thread associated with this Runnable
public void start() {
System.out.println("Starting Thread for Runnable: " + runnableName);
if (thread == null) {
thread = new Thread(this, runnableName + "-Thread"); // Pass 'this' Runnable and a name for the Thread
thread.start(); // This calls the run() method of this Runnable instance
}
}
// Optional method to allow joining this runnable's thread
public void join() throws InterruptedException {
if (thread != null && thread.isAlive()) {
thread.join();
}
}
}
2. Main Application to Demonstrate Threads
// ThreadDemo.java
public class ThreadDemo {
public static void main(String[] args) {
System.out.println("--- Main Thread Started ---");
// --- Method 1: Using Thread Class ---
System.out.println("\n--- Demonstrating extending Thread Class ---");
MyThread thread1 = new MyThread("Thread-A (Extends Thread)", 5);
MyThread thread2 = new MyThread("Thread-B (Extends Thread)", 3);
// Starting the threads - this calls their run() method in a new execution path
thread1.start();
thread2.start();
// --- Method 2: Using Runnable Interface ---
System.out.println("\n--- Demonstrating implementing Runnable Interface ---");
MyRunnable runnableTask1 = new MyRunnable("Runnable-X", 4);
MyRunnable runnableTask2 = new MyRunnable("Runnable-Y", 6);
// To run a Runnable, you need to create a Thread object and pass the Runnable instance to it.
Thread threadForRunnable1 = new Thread(runnableTask1, "Thread-For-Runnable-X"); // Explicitly naming the thread
Thread threadForRunnable2 = new Thread(runnableTask2); // Thread name will be auto-generated (e.g., "Thread-1")
// Starting the threads that execute the Runnable tasks
threadForRunnable1.start();
threadForRunnable2.start();
// Alternative way using the start() method within MyRunnable (if you added it)
MyRunnable runnableTask3 = new MyRunnable("Runnable-Z (Self-Starting)", 3);
runnableTask3.start(); // This encapsulates thread creation and starting
// --- Thread Management: Waiting for threads to complete (join()) ---
System.out.println("\nMain thread is now waiting for other threads to complete...");
try {
// Wait for thread1 (extends Thread) to complete
thread1.join();
System.out.println(thread1.getName() + " has finished.");
// Wait for thread2 (extends Thread) to complete
thread2.join();
System.out.println(thread2.getName() + " has finished.");
// Wait for threadForRunnable1 (executes Runnable) to complete
threadForRunnable1.join();
System.out.println(threadForRunnable1.getName() + " has finished.");
// Wait for threadForRunnable2 (executes Runnable) to complete
threadForRunnable2.join();
System.out.println(threadForRunnable2.getName() + " has finished.");
// Wait for runnableTask3's internally managed thread to complete
runnableTask3.join(); // Using the join method we added to MyRunnable
System.out.println("Runnable-Z-Thread has finished.");
} catch (InterruptedException e) {
System.err.println("Main thread interrupted while waiting: " + e.getMessage());
}
System.out.println("\n--- All threads have completed. Main Thread Exiting ---");
}
}
3. Explanation of Differences and Thread Management
Differences between extends Thread
and implements Runnable
:
Feature | extends Thread | implements Runnable |
---|---|---|
Inheritance | Your class becomes a Thread . | Your class defines a task that can be run by a Thread . |
Multiple Inheritance | Not possible if your class already needs to extend another class (Java doesn't support multiple class inheritance). | Possible. Your class can extend another class and still implement Runnable (and other interfaces). |
Object Type | The object is a specialized Thread . | The object is of your custom type, which is also a Runnable . A separate Thread object is needed to execute it. |
Code Organization | Tightly couples the task (code in run() ) with the Thread object itself. | Promotes loose coupling. The task (Runnable ) is separate from the execution mechanism (Thread ). This is generally better for design. |
Resource Sharing | Less straightforward if multiple threads need to execute the same instance of a task with shared state (though possible). | Easier to share the same Runnable instance among multiple Thread objects, allowing them to operate on the same task logic and potentially shared data (requires careful synchronization). |
Flexibility | Less flexible. | More flexible and generally the preferred approach. |
Usage | Simpler for very basic scenarios where the class doesn't need to extend anything else. | Recommended for most use cases due to better design and flexibility. Used heavily in thread pools and Executor frameworks. |
Why implements Runnable
is generally preferred:
- Overcomes Single Inheritance Limitation: If your class representing the task needs to extend another superclass, you can still make it runnable by implementing the
Runnable
interface. - Better Separation of Concerns: The
Runnable
interface separates the task to be performed from theThread
object that executes it. This leads to cleaner, more modular design. - Resource Sharing: A single
Runnable
object can be executed by multiple threads. This is useful if multiple threads need to perform the same operation, potentially on shared data (though synchronization then becomes crucial). - Compatibility with Executor Framework: Java's
Executor
framework (e.g.,ThreadPoolExecutor
) works primarily withRunnable
(andCallable
) tasks, making it the more idiomatic choice for modern concurrent programming.
Thread Creation and Management Demonstrated:
Creation:
extends Thread
:MyThread thread1 = new MyThread(...)
;implements Runnable
:MyRunnable task = new MyRunnable(...)
;Thread threadForTask = new Thread(task, "OptionalThreadName");
Starting a Thread (
start()
):thread1.start();
threadForTask.start();
- Important: You must call
start()
to create a new thread of execution and invoke therun()
method. If you callrun()
directly (e.g.,thread1.run()
), it will execute in the current thread, not a new one.
The
run()
Method:- This is where the actual logic of the thread goes. It's the entry point for the new thread.
Thread Naming:
- Threads can be given names (e.g.,
new Thread(runnable, "MyWorkerThread")
orthread.setName("NewName")
). This is very useful for debugging. If not named, Java assigns a default name like "Thread-0", "Thread-1", etc. Thread.currentThread().getName()
can be used insiderun()
to get the name of the currently executing thread.
- Threads can be given names (e.g.,
Sleeping (
Thread.sleep(milliseconds)
):Thread.sleep(50);
pauses the current thread for the specified duration.- It can throw an
InterruptedException
, which must be caught or declared. This exception is thrown if another thread interrupts the sleeping thread.
Waiting for a Thread to Die (
join()
):thread1.join();
- When a thread calls
anotherThread.join()
, the calling thread (e.g., themain
thread in our demo) will pause and wait untilanotherThread
completes its execution (i.e., itsrun()
method finishes). - This is crucial for scenarios where one thread's work depends on the completion of another.
join()
can also throw anInterruptedException
.
Checking if Alive (
isAlive()
):thread.isAlive()
returnstrue
if the thread has been started and has not yet died (completed itsrun()
method). (Used inMyRunnable.join()
).
To Compile and Run:
- Save the code into three files:
MyThread.java
,MyRunnable.java
, andThreadDemo.java
. - Compile:bash
javac MyThread.java MyRunnable.java ThreadDemo.java
- Run:bash
java ThreadDemo
You will see interleaved output from the different threads, demonstrating concurrent execution. The join()
calls ensure that the "All threads have completed" message appears only after all worker threads have finished.