Java - Creating a Thread

Hello there, future Java wizards! Today, we're going to dive into the exciting world of Java threads. Don't worry if you're new to programming; I'll guide you through this journey step by step, just like I've done for countless students over my years of teaching. So, grab your favorite beverage, get comfy, and let's embark on this threading adventure together!

Java - Creating a Thread

What is a Thread?

Before we jump into creating threads, let's understand what a thread actually is. Imagine you're in a kitchen, cooking a delicious meal. You're chopping vegetables, stirring the sauce, and checking the oven all at once. Each of these tasks is like a thread in a computer program - they're separate tasks that can run concurrently.

In Java, a thread is the smallest unit of execution within a program. It allows different parts of your program to run simultaneously, making your applications more efficient and responsive.

Why Use Threads?

You might be wondering, "Why should I bother with threads?" Well, let me tell you a little story. Years ago, I was developing a simple image processing application for a photography club. The app worked fine for small images, but when users tried to process large, high-resolution photos, it would freeze up. The culprit? Everything was running on a single thread! By implementing multi-threading, we were able to process images in the background while keeping the user interface responsive. The photography club members were thrilled, and I learned a valuable lesson about the power of threads.

Creating a Thread in Java

In Java, there are two main ways to create a thread:

  1. Implementing the Runnable interface
  2. Extending the Thread class

Let's explore both methods with some hands-on examples!

Method 1: Implementing the Runnable Interface

This is often considered the preferred way to create a thread because it doesn't require you to subclass Thread, allowing your class to extend other classes if needed.

Here's a simple example:

public class MyRunnable implements Runnable {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Thread using Runnable: " + i);
            try {
                Thread.sleep(1000); // Pause for 1 second
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted.");
            }
        }
    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();

        // Main thread
        for (int i = 1; i <= 5; i++) {
            System.out.println("Main Thread: " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Main thread interrupted.");
            }
        }
    }
}

Let's break this down:

  1. We create a class MyRunnable that implements the Runnable interface.
  2. We override the run() method, which defines what the thread will do when it's started.
  3. In the main method, we create an instance of MyRunnable and pass it to a new Thread object.
  4. We call start() on the thread to begin its execution.
  5. The main thread also prints numbers, demonstrating how both threads run concurrently.

When you run this program, you'll see the numbers from both threads interleaved, showing that they're running simultaneously!

Method 2: Extending the Thread Class

This method involves creating a subclass of Thread and overriding its run() method. Here's an example:

public class MyThread extends Thread {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Thread extending Thread class: " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted.");
            }
        }
    }

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

        // Main thread
        for (int i = 1; i <= 5; i++) {
            System.out.println("Main Thread: " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Main thread interrupted.");
            }
        }
    }
}

The key differences here are:

  1. Our class MyThread extends Thread instead of implementing Runnable.
  2. We can directly call start() on our MyThread object, without needing to create a separate Thread instance.

Comparing the Two Methods

Both methods achieve the same result, but there are some considerations:

Feature Runnable Interface Extending Thread Class
Flexibility Can extend other classes Cannot extend other classes (Java doesn't support multiple inheritance)
Separation of Concerns Separates the task from the thread Combines the task and thread in one class
Reusability More reusable Less reusable
Resource Efficiency More efficient (can be used with thread pools) Less efficient

In most cases, implementing Runnable is considered better practice because it offers more flexibility and better aligns with object-oriented design principles.

Conclusion

Congratulations! You've just taken your first steps into the world of Java threading. We've covered the basics of what threads are, why they're useful, and how to create them using two different methods. Remember, like learning to juggle, mastering threads takes practice. Don't be discouraged if it doesn't click immediately - keep experimenting and you'll get there!

In my next lesson, we'll dive deeper into thread synchronization and communication between threads. Until then, happy coding, and may your threads always run smoothly!

Credits: Image by storyset