Java > Concurrency and Multithreading > Thread Basics > Creating Threads: Thread Class vs Runnable Interface

Creating Threads: Thread Class vs Runnable Interface

This example demonstrates two fundamental ways to create threads in Java: extending the Thread class and implementing the Runnable interface. Understanding the differences and advantages of each approach is crucial for effective multithreaded programming.

Extending the Thread Class

This code defines a class MyThread that extends the Thread class. The run() method contains the code that will be executed in the new thread. The start() method is invoked to begin the thread's execution. Each thread will print a message to the console indicating it is running. Extending the Thread class is straightforward but limits inheritance options since Java only supports single inheritance.

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getName() + " running (Thread Class).");
    }

    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        thread1.start();
        thread2.start();
    }
}

Implementing the Runnable Interface

This code defines a class MyRunnable that implements the Runnable interface. The run() method, as before, contains the code executed by the thread. To start the thread, we create a Thread object and pass an instance of MyRunnable to its constructor. This approach promotes loose coupling and allows your class to inherit from another class, avoiding the single inheritance limitation. It is also more flexible since multiple threads can share the same Runnable object.

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getName() + " running (Runnable Interface).");
    }

    public static void main(String[] args) {
        MyRunnable runnable1 = new MyRunnable();
        MyRunnable runnable2 = new MyRunnable();
        Thread thread1 = new Thread(runnable1);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
    }
}

Concepts Behind the Snippets

The core concept is concurrent execution. Threads allow multiple tasks to run seemingly simultaneously within a single program. This is achieved by time-slicing, where the CPU rapidly switches between different threads. The Thread class provides the basic functionality for creating and managing threads, while the Runnable interface provides a way to define the task that a thread will execute.

Real-Life Use Case Section

Downloading multiple files simultaneously. Instead of downloading files sequentially, you can create a thread for each file, significantly reducing the overall download time. Another example is handling multiple client requests in a server application. Each request can be processed by a separate thread, allowing the server to handle multiple clients concurrently.

Best Practices

  • Prefer implementing Runnable over extending Thread for greater flexibility and adherence to object-oriented principles.
  • Always handle exceptions within the run() method to prevent unexpected thread termination.
  • Use thread-safe data structures when multiple threads access shared resources to avoid race conditions and data corruption.
  • Consider using higher-level concurrency utilities like ExecutorService for managing thread pools and simplifying thread management.

Interview Tip

Be prepared to explain the differences between extending Thread and implementing Runnable, including the advantages and disadvantages of each. Also, be ready to discuss potential problems like race conditions and deadlocks in multithreaded programs, and how to prevent them.

When to Use Them

  • Use extending Thread when your class solely needs to be a thread and doesn't require inheriting behavior from another class. This is less common.
  • Use implementing Runnable when your class already extends another class or needs to implement other interfaces. This is the more common and preferred approach. Also use Runnable when multiple threads need to share the same task logic.

Memory Footprint

Each thread consumes memory for its stack, which stores local variables and method call information. Creating too many threads can lead to excessive memory consumption and performance degradation. Using thread pools can help manage memory consumption by reusing threads instead of creating new ones for each task.

Alternatives

  • ExecutorService: A higher-level abstraction for managing thread pools.
  • ForkJoinPool: Designed for parallelizing recursive algorithms.
  • Reactive Programming (e.g., RxJava, Project Reactor): A paradigm for asynchronous and event-based programming that can simplify concurrency management.
  • Virtual Threads (Project Loom): Lightweight threads managed by the JVM, offering improved concurrency without the overhead of traditional OS threads.

Pros of Runnable Interface

  • Avoids the single inheritance limitation of Java.
  • Promotes loose coupling.
  • More flexible – multiple threads can share the same Runnable object.
  • Facilitates the use of thread pools.

Cons of Extending Thread Class

  • Violates single responsibility principle.
  • Tighter coupling.
  • Limits inheritance possibilities.

FAQ

  • What is the difference between start() and run()?

    The start() method creates a new thread and then calls the run() method in that new thread. Calling run() directly simply executes the code within the current thread; it does not create a new thread.
  • Why should I prefer Runnable over Thread?

    Implementing Runnable promotes better object-oriented design by separating the task to be executed from the thread management. It also allows your class to inherit from another class, which is not possible when extending Thread.
  • How do I pass data to a thread?

    When implementing Runnable, you can pass data to the thread through the constructor of the Runnable class or by setting fields of the Runnable object before passing it to the Thread constructor. When extending Thread, you can create fields in your Thread subclass and set them before calling start().