Java Full Course: Mastering the Language Thread Creation in Java

Thread Creation in Java

1. Introduction to Threads

A thread is a lightweight unit of execution within a process. Java supports multithreading, allowing multiple threads to run concurrently, sharing the same memory space. Creating threads is the first step toward building responsive, parallel, or background‑task applications.

Java provides two primary ways to create a thread:

  • Extend the Thread class.
  • Implement the Runnable interface.

Both approaches require you to define the code that the thread will execute in its run() method. The run() method contains the task logic.


2. Method 1: Extending the Thread Class

You create a new class that extends java.lang.Thread and override its run() method. To start the thread, create an instance of your class and call start() (never call run() directly).

Example:

class MyThread extends Thread { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadDemo { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); // starts a new thread and invokes run() t2.start(); } }

Key points:

  • The class can no longer extend any other class (Java single inheritance).
  • The run() method is overridden.
  • Starting the thread is done via start().
  • Each thread gets its own call stack and runs independently.

3. Method 2: Implementing the Runnable Interface

Implement java.lang.Runnable and define the run() method. Then pass an instance of this class to a Thread constructor.

Example:

class MyRunnable implements Runnable { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class RunnableDemo { public static void main(String[] args) { Runnable task = new MyRunnable(); Thread t1 = new Thread(task); Thread t2 = new Thread(task); t1.start(); t2.start(); } }

Key points:

  • The class can extend another class (since Runnable is an interface).
  • The Runnable object is passed to the Thread constructor.
  • Multiple threads can share the same Runnable object (useful for sharing data).
  • This approach is generally preferred because it separates the task from the threading mechanism.

4. Comparison: Thread vs Runnable

AspectExtending ThreadImplementing Runnable
InheritanceCannot extend any other classCan extend another class
Code reuseTask code is tied to the thread classTask can be reused with different threads or executors
Resource sharingSeparate instances for each threadMultiple threads can share the same Runnable instance
Separation of concernsCombines task logic with thread managementSeparates task from thread management
Recommended usageRarely usedPreferred approach

Note: Since Java 8, Runnable is a functional interface (single abstract method), so you can use lambda expressions to create threads even more concisely.


5. Creating Threads with Lambda Expressions (Java 8+)

Because Runnable has a single abstract method run(), it can be implemented using a lambda expression.

public class LambdaThread { public static void main(String[] args) { Runnable task = () -> { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); try { Thread.sleep(500); } catch (InterruptedException e) {} } }; Thread t1 = new Thread(task, "Thread-A"); Thread t2 = new Thread(task, "Thread-B"); t1.start(); t2.start(); } }

You can even inline the lambda directly in the Thread constructor:

new Thread(() -> { // task code }).start();

This syntax is clean and widely used in modern Java applications.


6. Creating Threads with ExecutorService (Higher‑Level)

For more advanced thread management (e.g., thread pooling, scheduling), the java.util.concurrent package provides the ExecutorService framework. It abstracts thread creation and lifecycle.

Example using a fixed thread pool:

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorDemo { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); Runnable task = () -> { System.out.println(Thread.currentThread().getName() + " is running"); }; // Submit tasks executor.submit(task); executor.submit(task); executor.submit(task); executor.shutdown(); // stop accepting new tasks } }

This approach is preferred in production code because it separates task submission from thread management and reuses threads efficiently.


7. Common Pitfalls and Best Practices

PitfallExplanation / Solution
Calling run() instead of start()run() executes in the current thread, not a new one. Always call start().
Starting a thread twiceA thread cannot be restarted; attempting to start() a terminated thread throws IllegalThreadStateException.
Not handling InterruptedExceptionMethods like sleep() throw InterruptedException. Always handle it properly (e.g., restore interrupt status).
Using Thread.stop() (deprecated)It is unsafe and deprecated. Use flags or interruption to stop threads gracefully.
Sharing mutable data without synchronizationLeads to race conditions. Use synchronization, atomic classes, or concurrent collections.
Creating too many threadsOverhead degrades performance. Use thread pools.
Tip

[!TIP] Best Practices:

  • Prefer Runnable over extending Thread for flexibility.
  • Use lambda expressions for concise task definitions.
  • For long‑running or repeated tasks, use ExecutorService.
  • Always give threads meaningful names (via constructor or setName()) for easier debugging.
  • Handle interruptions gracefully; don’t swallow them.
  • Avoid starting threads in constructors; it can expose partially constructed objects.

8. Complete Example: Thread Creation Showcase

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadCreationDemo { // 1. Extending Thread static class WorkerThread extends Thread { @Override public void run() { System.out.println("WorkerThread: " + getName() + " is working"); } } // 2. Implementing Runnable static class WorkerRunnable implements Runnable { @Override public void run() { System.out.println("WorkerRunnable: " + Thread.currentThread().getName() + " is working"); } } public static void main(String[] args) { // Using Thread subclass WorkerThread t1 = new WorkerThread(); t1.setName("Thread-Extend"); t1.start(); // Using Runnable Thread t2 = new Thread(new WorkerRunnable(), "Thread-Runnable"); t2.start(); // Using lambda Thread t3 = new Thread(() -> System.out.println("Lambda thread: " + Thread.currentThread().getName()), "Thread-Lambda"); t3.start(); // Using ExecutorService ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(() -> System.out.println("Executor task: " + Thread.currentThread().getName())); executor.shutdown(); } }

Possible output (order may vary):

WorkerThread: Thread-Extend is working WorkerRunnable: Thread-Runnable is working Lambda thread: Thread-Lambda is working Executor task: pool-1-thread-1 is working

9. Key Points to Remember

  • Two ways to create a thread: extend Thread or implement Runnable.
  • Start a thread with start(), never call run() directly.
  • Runnable is preferred because it separates task logic from thread management and allows extending another class.
  • Lambda expressions make Runnable code concise.
  • ExecutorService provides a modern, scalable way to manage threads.
  • Always name your threads for debugging.
  • Handle interruptions properly.
  • Avoid mutable shared data without synchronization.
Hi! Need help with studies? 👋
AI