04 · Thread Creation & Lifecycle

Level: Beginne

Pre-reading: 01 · Concurrency vs Parallelism


Runnable vs Callable

Runnable (Traditional)

@FunctionalInterface
public interface Runnable {
    void run();  // No return value, no checked exceptions
}

Characteristics:

  • ❌ No return value
  • ❌ Cannot throw checked exceptions
  • ✅ Used with Thread directly
  • ✅ Used with ExecutorService

Usage:

// with Thread
Runnable task = () -> System.out.println("Running");
Thread thread = new Thread(task);
thread.start();

// with ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> System.out.println("Task"));
executor.shutdown();

Callable (Modern, Preferred for Tasks)

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;  // Returns V, can throw Exception
}

Characteristics:

  • ✅ Returns a value of type V
  • ✅ Can throw checked exceptions
  • ❌ Cannot use with Thread directly
  • ✅ Used with ExecutorService

Usage:

// Returns result
Callable<Integer> task = () -> {
    Thread.sleep(100);
    return 42;  // Return value
};

ExecutorService executor = Executors.newFixedThreadPool(4);
Future<Integer> future = executor.submit(task);

try {
    Integer result = future.get();  // Waits for result
    System.out.println("Result: " + result);
} catch (ExecutionException e) {
    // Exception thrown in task
}

Comparison

Feature Runnable Callable
Method void run() V call()
Return None Returns V
Checked Except Cannot throw Can throw
With Thread
With Executor
For computations

Thread Creation Methods

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

// Usage
Thread thread = new MyThread();
thread.start();

Problems:

  • ❌ Java doesn't support multiple inheritance
  • ❌ Inflexible: if you extend Thread, you can't extend another class
  • ✅ Only use if absolutely necessary

Method 2: Implementing Runnable (✅ Preferred)

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

// Usage
Thread thread = new Thread(new MyTask());
thread.start();

// Or with lambda (Java 8+)
Thread thread2 = new Thread(() -> {
    System.out.println("Task running");
});
thread2.start();

Advantages:

  • ✅ Can extend other classes
  • ✅ More flexible design
  • ✅ Separates task logic from thread mechanism

Method 3: ExecutorService (✅ Most Modern)

ExecutorService executor = Executors.newFixedThreadPool(4);

// Submit Runnable
executor.submit(() -> {
    System.out.println("Task 1");
});

// Submit Callable with return value
Future<String> future = executor.submit(() -> {
    return "Task 2 result";
});

executor.shutdown();
String result = future.get();

Advantages:

  • ✅ Automatic thread pool management
  • ✅ Better resource usage
  • ✅ Easy to submit many tasks
  • ✅ Can retrieve results with Future

Method 4: Thread Factory Pattern (Utility Version)

// Named thread creation
Thread thread = Thread.ofPlatform()
    .name("worker-1")
    .start(() -> {
        System.out.println("Running in: " + Thread.currentThread().getName());
    });

// Virtual thread (Java 21+)
Thread vthread = Thread.ofVirtual()
    .name("virtual-1")
    .start(() -> {
        System.out.println("Virtual thread");
    });

Thread Lifecycle

State Diagram

stateDiagram-v2
    [*] --> NEW
    NEW --> RUNNABLE: start()
    RUNNABLE --> WAITING: wait() or join()
    WAITING --> RUNNABLE: notify() or notifyAll()
    RUNNABLE --> TIMED_WAITING: sleep(ms) or timed wait/join
    TIMED_WAITING --> RUNNABLE: timeout or interrupt
    RUNNABLE --> BLOCKED: synchronized lock not acquired
    BLOCKED --> RUNNABLE: lock acquired
    RUNNABLE --> TERMINATED: run() ends<br/>or exception
    [*] --> TERMINATED

State Descriptions

State Code Meaning
NEW Thread t = new Thread(...) Created, not started
RUNNABLE t.start() Ready or running
BLOCKED Inside synchronized Waiting for lock
WAITING t.wait() or join() Indefinite wait
TIMED_WAITING sleep(ms) Waiting with timeout
TERMINATED run() ends Finished

Code Example

Thread thread = new Thread(() -> {
    System.out.println("State: " + Thread.currentThread().getState());
    // RUNNABLE
});
System.out.println("State before start: " + thread.getState());  // NEW

thread.start();
System.out.println("State after start: " + thread.getState());  // RUNNABLE (or TERMINATED)

try {
    Thread.sleep(100);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}
System.out.println("Final state: " + thread.getState());  // TERMINATED

Thread Naming and Identification

Getting Thread Info

Thread current = Thread.currentThread();

System.out.println("Name: " + current.getName());      // Thread-0, Thread-1, etc.
System.out.println("ID: " + current.getId());          // Unique long ID
System.out.println("Priority: " + current.getPriority()); // 1-10
System.out.println("Is Alive: " + current.isAlive());  // true if running
System.out.println("Is Daemon: " + current.isDaemon()); // true if daemon
System.out.println("State: " + current.getState());    // RUNNABLE, etc.

Setting Thread Names

Thread thread = new Thread(() -> {
    System.out.println("Running in: " + Thread.currentThread().getName());
});

thread.setName("my-worker-thread");
thread.start();
// Output: Running in: my-worker-thread

Daemon vs Non-Daemon Threads

Non-Daemon Threads (Default)

Non-daemon threads are "important threads". The JVM waits for all non-daemon threads to finish before exiting.

Thread nonDaemon = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("Non-daemon: " + i);
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
    }
});

nonDaemon.setDaemon(false);  // Explicit (default anyway)
nonDaemon.start();

// Main thread finishes, but JVM waits for nonDaemon to finish
System.out.println("Main finished");
// Output:
// Main finished
// Non-daemon: 0
// (waits 1 second)
// Non-daemon: 1
// ... 4
// (JVM exits once all non-daemon threads finish)

Daemon Threads

Daemon threads are "background threads". The JVM exits immediately even if daemon threads still running.

Thread daemon = new Thread(() -> {
    while (true) {
        System.out.println("Daemon working...");
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
    }
});

daemon.setDaemon(true);
daemon.start();

System.out.println("Main finished");
// Output:
// Daemon working...
// Main finished
// (JVM exits, daemon terminates abruptly)

When to Use Daemon Threads

Daemon threads are good for:

  • Background cleanup
  • Periodic tasks (garbage collection, log flushing)
  • Tasks that are "nice-to-have" but not critical

Daemon threads are bad for:

  • I/O operations that need to complete
  • Database transactions
  • Any task where cleanup is important

-

Comparison

Aspect Non-Daemon Daemon
JVM waits? Yes No
When to use Main work Background work
Cleanup Guaranteed Not guaranteed
Default Non-daemon Can set with setDaemon()
Set before Must call before start() Must call before start()

Key Takeaways

  • Runnable: No return, no exceptions, use with Thread or Executor
  • Callable: Returns value, can throw exceptions, use with Executor only
  • ExecutorService: Preferred modern way to create threads
  • Thread states: NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED
  • Daemon threads: Background tasks; JVM exits immediately
  • Non-daemon threads: Important tasks; JVM waits for them

📚 Read the Original Blog Post

For more details and examples, read:


Should I extend Thread or implement Runnable?

Implement Runnable (or use lambda). Extending Thread is inflexible since Java doesn't support multiple inheritance.

What's the difference between state RUNNABLE and actually running?

RUNNABLE means ready to run OR currently running. A thread in RUNNABLE state may not actually be executing if the OS scheduled another thread.

Can I call start() twice on the same Thread?

No. It throws IllegalThreadStateException. Create a new Thread object if you need to run it again.