Post

Creating and Running Threads in Java



Introduction

Multithreading is an essential feature in Java that allows developers to execute multiple tasks simultaneously. To create and manage threads in Java, you can either extend the Thread class or implement the Runnable interface. Both approaches have their advantages and are suitable for different use cases. In this article, we’ll compare these two methods and discuss their differences, benefits, and best practices.

What is a Thread?

In Java, a thread is the smallest unit of execution within a process. By default, every Java application has at least one thread, the main thread, responsible for executing the main() method. However, you can create additional threads to perform concurrent tasks, improving the efficiency and responsiveness of your application.

Extending the Thread Class

One of the simplest ways to create a thread is by extending the Thread class and overriding its run() method. The run() method contains the code that will be executed by the thread when it starts. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running.");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();  // Starts the new thread
    }
}

In this example, we create a class MyThread that extends Thread and overrides the run() method to define the task the thread will perform. To start the thread, we call the start() method, which internally invokes the run() method on a new thread of execution.

Pros of Extending Thread

  • Simpler to use:
    If you don’t need to inherit from another class, extending the Thread class is a straightforward way to create a thread.
  • Direct access to thread methods:
    Since the class is a subclass of Thread, you can directly access other thread-related methods, such as getId() and getName().

Cons of Extending Thread

  • Lack of flexibility:
    Java does not support multiple inheritance, so if your class already extends another class, you cannot extend Thread.
  • Less separation of concerns:
    Extending Thread mixes the business logic (what the thread does) with thread control logic (how the thread behaves), making the code less reusable.

Implementing the Runnable Interface

The more flexible and common approach to creating threads is by implementing the Runnable interface. This method decouples the thread’s behavior from the thread control, as the thread logic is defined in a separate class. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable is running.");
    }
}

public class Main {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();  // Starts the new thread
    }
}

In this example, the class MyRunnable implements Runnable and provides the implementation of the run() method. We then create a Thread object, passing an instance of MyRunnable to the Thread constructor, and start the thread using the start() method.

Pros of Implementing Runnable

  • Supports multiple inheritance:
    Since your class implements Runnable instead of extending Thread, it can extend other classes, making your design more flexible.
  • Separation of concerns:
    The thread’s behavior is separated from its execution, which improves code reusability and modularity.
  • Thread reuse:
    The same Runnable object can be executed by multiple threads if needed, increasing flexibility.

Cons of Implementing Runnable

  • Slightly more complex syntax:
    You need to pass a Runnable object to a Thread instance, which adds a small amount of boilerplate code compared to extending Thread.

Key Differences Between Extending Thread and Implementing Runnable

  1. Inheritance
    • Extending Thread:
      You can’t extend another class because Java does not support multiple inheritance.
    • Implementing Runnable:
      Your class can implement Runnable and still extend another class, providing more design flexibility.
  2. Separation of Concerns
    • Extending Thread:
      Combines both the thread behavior and execution control, making the code harder to reuse.
    • Implementing Runnable:
      Separates the thread’s behavior from its execution, promoting better design and code reusability.
  3. Flexibility
    • Extending Thread:
      Less flexible, especially in scenarios where a class needs to inherit from another class.
    • Implementing Runnable:
      More flexible and commonly used in large applications, particularly when working with thread pools or ExecutorService.
  4. Memory Overhead
    • Extending Thread:
      Each thread object contains thread-related data and methods, which can lead to higher memory usage.
    • Implementing Runnable:
      More lightweight, as it doesn’t require the overhead of a Thread object for each task, leading to better performance when creating many threads.

When to Use Each Approach

  • Use Thread:
    If your class has no need to extend another class and the task is simple or limited in scope, extending Thread is a quick way to create a thread.
  • Use Runnable:
    When you need flexibility, especially when your class already extends another class or if you’re implementing a large, scalable application where thread reuse and modularity are important.

Best Practices

  • Prefer Runnable over Thread:
    In most real-world applications, implementing Runnable is preferred for better design flexibility and reusability.
  • Use Executors for Thread Management:
    Instead of manually creating and managing threads, consider using the ExecutorService framework, which simplifies thread management and improves resource utilization.
1
2
3
4
5
6
7
8
9
10
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.submit(new MyRunnable());
        executor.shutdown();
    }
}

By using executors, you can manage thread pools and reduce the overhead associated with creating and destroying threads.

Conclusion

Both extending Thread and implementing Runnable are viable ways to create threads in Java. However, for most cases, implementing Runnable is the preferred approach due to its flexibility and better design principles. It allows you to separate the concerns of the task from thread management and provides a cleaner, more scalable solution, especially when used with Java’s ExecutorService.

© 2024 Java Tutorial Online. All rights reserved.