Concurrency

51. Creating and Starting Threads

Java threads can be created by subclassing Thread or implementing Runnable; always call start() (not run()) to launch a new OS thread; daemon threads do not prevent JVM shutdown.

Java 21 (core language)

Explanation

Two classic approaches: extend Thread and override run(), or implement Runnable and pass it to Thread. The Runnable approach is preferred โ€” it keeps the task separate from the thread lifecycle and allows the task class to extend something else.

start() vs run(): start() creates a new OS thread and asynchronously calls run() on it. Calling run() directly just executes the method synchronously on the current thread โ€” no new thread is created. This is the most common threading mistake.

Daemon threads: a daemon thread (thread.setDaemon(true) before start()) does not prevent the JVM from exiting when all non-daemon threads finish. Daemon threads are abruptly stopped when the JVM shuts down โ€” never use them for tasks that must complete (I/O, database writes). The main thread and threads it starts are non-daemon by default.

Example

public class ThreadCreateStart {
    // Option 1: extend Thread (tightly coupled โ€” less preferred)
    static class CounterThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println(getName() + " count: " + i);
            }
        }
    }

    // Option 2: Runnable (preferred โ€” decoupled)
    static class CounterTask implements Runnable {
        private final String name;
        CounterTask(String name) { this.name = name; }

        @Override
        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println(name + " count: " + i);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // start() โ€” correct: launches new thread
        Thread t1 = new CounterThread();
        t1.start();

        // Runnable + Thread constructor
        Thread t2 = new Thread(new CounterTask("t2"));
        t2.start();

        // Lambda as Runnable (most concise modern style)
        Thread t3 = new Thread(() -> System.out.println("Lambda thread: " + Thread.currentThread().getName()));
        t3.start();

        // Daemon thread: will be killed if main ends first
        Thread daemon = new Thread(() -> {
            try { Thread.sleep(5000); System.out.println("Daemon done (may never print)"); }
            catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        });
        daemon.setDaemon(true);
        daemon.start();

        // join: wait for t1 and t2 to finish
        t1.join();
        t2.join();
        t3.join();
        System.out.println("All threads finished");
    }
}

Sources