Skip to content
intermediatePhase 17 · Java Multithreading

Threads and Runnable

Create threads with Thread class and Runnable interface.

1h
2 problems
Topic Progress0%

Extending Thread Class

Extending the Thread Class

The simplest way to create a thread is by extending java.lang.Thread.

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(1000);  // Sleep 1 second
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        
        t1.start();  // Creates new thread, calls run()
        t2.start();  // Creates new thread, calls run()
        
        // Output (interleaved):
        // Thread-0: 0
        // Thread-1: 0
        // Thread-0: 1
        // Thread-1: 1
        // ...
    }
}

Key points:

  • Override run() with your task code
  • Call start() to create a new thread
  • Never call run() directly (runs on same thread)
  • Each thread gets a default name (Thread-0, Thread-1, etc.)

Implementing Runnable Interface

Implementing Runnable Interface

Runnable is a functional interface with a single run() method. This is the preferred approach because:

  1. Java doesn't support multiple inheritance
  2. Runnable separates task from thread
  3. Can be used with ExecutorService
// Lambda approach (Java 8+)
public class RunnableDemo {
    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        
        Thread t1 = new Thread(task, "Worker-1");
        Thread t2 = new Thread(task, "Worker-2");
        
        t1.start();
        t2.start();
    }
}

Class-based approach:

public class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Task running on: " + Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        Thread t = new Thread(new MyTask());
        t.start();
    }
}

When to use which:

  • Runnable: Preferred, more flexible, supports lambda
  • Thread: Only when you need to override Thread methods (getName, setPriority)

Thread Lifecycle

Thread Lifecycle States

A thread can be in one of six states:

                    start()
    NEW ──────────────> RUNNABLE ──────────────> TERMINATED
                         │    ▲                    │
              lock/      │    │  notify/           │
              wait       │    │  timeout           │
                         ▼    │                    │
                       BLOCKED/WAITING/TIMED_WAITING

1. NEW - Thread created but not started

Thread t = new Thread(() -> {});  // NEW state

2. RUNNABLE - Ready to run, waiting for CPU

t.start();  // Moves to RUNNABLE

3. BLOCKED - Waiting for monitor lock

synchronized (lock) {
    // If another thread holds lock, this thread is BLOCKED
}

4. WAITING - Waiting indefinitely for another thread

thread.wait();      // WAITING
thread.join();      // WAITING (if thread is still running)
LockSupport.park(); // WAITING

5. TIMED_WAITING - Waiting for specified time

Thread.sleep(1000);        // TIMED_WAITING
thread.wait(1000);         // TIMED_WAITING
thread.join(1000);         // TIMED_WAITING

6. TERMINATED - Execution complete

// After run() finishes

Check state:

Thread.State state = thread.getState();
System.out.println(state);  // RUNNABLE, BLOCKED, etc.

Common Thread Methods

Common Thread Methods

sleep(long millis) - Pause current thread

Thread.sleep(1000);  // Sleep 1 second
Thread.sleep(1000, 500000);  // 1 second + 500 nanoseconds
// Throws InterruptedException

join() - Wait for thread to complete

Thread t1 = new Thread(() -> {
    try { Thread.sleep(2000); } catch (Exception e) {}
    System.out.println("Done");
});
t1.start();
t1.join();  // Main thread waits for t1 to finish
System.out.println("t1 completed");

// join with timeout
t1.join(5000);  // Wait max 5 seconds

yield() - Hint to scheduler to give other threads a chance

Thread.yield();  // Hint only, no guarantee

interrupt() - Interrupt a sleeping/waiting thread

Thread t = new Thread(() -> {
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        System.out.println("Thread interrupted!");
    }
});
t.start();
Thread.sleep(100);
t.interrupt();  // Wakes up thread with InterruptedException

setPriority() - Set thread priority (1-10)

t.setPriority(Thread.MAX_PRIORITY);  // 10
t.setPriority(Thread.MIN_PRIORITY);  // 1
t.setPriority(Thread.NORM_PRIORITY); // 5 (default)

Thread.currentThread() - Get current thread reference

Thread current = Thread.currentThread();
System.out.println(current.getName());

Best Practices

Thread Best Practices

1. Prefer Runnable/Callable over extending Thread

// Bad - can't extend other classes
class MyThread extends Thread { ... }

// Good - flexible, supports lambda
Runnable task = () -> doWork();
new Thread(task).start();

// Better - use ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(task);

2. Always handle InterruptedException

// Bad - swallows exception
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // nothing
}

// Good - restores interrupt status
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();  // Restore status
    // or propagate
}

3. Use meaningful thread names

// Bad
new Thread(() -> {}).start();  // Thread-0

// Good
new Thread(() -> {}, "OrderProcessor-1").start();

4. Don't rely on yield()

// yield() is just a hint
// Scheduler may ignore it
// Use proper synchronization instead

5. Use daemon threads for background tasks

Thread daemon = new Thread(() -> {
    while (true) {
        try { Thread.sleep(1000); } catch (Exception e) {}
        System.out.println("Background task");
    }
});
daemon.setDaemon(true);  // Dies when main thread exits
daemon.start();

6. Never start a thread twice

Thread t = new Thread(() -> {});
t.start();
t.start();  // IllegalThreadStateException!

Practice Problems

0/2solved
Thread Ordering

What is the output of this code? Can you guarantee the order? ```java public class ThreadOrder { public static void main(String[] args) { Thread t1 = new Thread(() -> System.out.println("A")); Thread t2 = new Thread(() -> System.out.println("B")); Thread t3 = new Thread(() -> System.out.println("C")); t1.start(); t2.start(); t3.start(); } } ```

Solution
Output is **non-deterministic**. It could be ABC, ACB, BAC, BCA, CAB, or CBA.

To guarantee order:
```java
t1.start();
t1.join();  // Wait for t1

Thread t2 = new Thread(() -> System.out.println("B"));
t2.start();
t2.join();  // Wait for t2

Thread t3 = new Thread(() -> System.out.println("C"));
t3.start();
```

This ensures A → B → C ordering.
start() vs run()

What is the difference between these two code snippets? ```java // Version 1 MyThread t = new MyThread(); t.start(); // Version 2 MyThread t = new MyThread(); t.run(); ```

Solution
**Version 1 (`start()`):**
- Creates a new thread
- `run()` executes on the new thread
- Output: `Thread-0: ...` (new thread name)
- True parallelism

**Version 2 (`run()`):**
- No new thread created
- `run()` executes on the calling thread (main)
- Output: `main: ...` (main thread name)
- Sequential execution

**Key rule:** Always call `start()` to create a new thread. Calling `run()` directly is a common mistake.

Quiz

1. What happens when you call run() instead of start()?

Question 1 options

2. What is the difference between Runnable and Thread?

Question 2 options

3. What method makes the current thread wait for another thread to finish?

Question 3 options

4. What are the six thread states?

Question 4 options

Flashcards

Question

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

Answer

start() creates a new thread and calls run() on it. run() executes on the current thread without creating a new one.

Question

What does Thread.join() do?

Answer

Makes the current thread wait until the specified thread completes execution.

Question

What is the difference between sleep() and wait()?

Answer

sleep() is a static method that pauses the current thread. wait() releases the object lock and waits for notify().

Question

When should you use a daemon thread?

Answer

For background tasks that should stop when all non-daemon threads finish. Use setDaemon(true) before start().

Question

What is Java Threads and Concurrency?

Answer

Java Threads and Concurrency is a key concept in Java programming.

Revision Notes

Key Takeaways

  • 1.Always call start(), never call run() directly
  • 2.Runnable is preferred over extending Thread
  • 3.Thread lifecycle has 6 states
  • 4.join() waits for a thread to complete

Interview Tips

  • Explain the difference between start() and run()
  • Know all 6 thread states
  • Explain why Runnable is preferred (multiple inheritance)
  • Discuss daemon threads and their use cases

Cheat Sheet

Threads Cheat Sheet

Creating Threads

  1. Extending Thread class
  2. Implementing Runnable (preferred)
  3. Using Callable (for return values)

Thread Methods

  • start() → new thread
  • run() → current thread
  • sleep(ms) → pause
  • join() → wait for completion
  • yield() → hint to scheduler
  • interrupt() → wake up sleeping thread

States

NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED

Best Practices

  • Prefer Runnable/Callable
  • Handle InterruptedException
  • Use meaningful thread names
  • Never start thread twice