Skip to content
  • Threads & Runnable · Java

Threads & Runnable

LearnViaNotes @learnvianotes· 6 min read

A thread is an independent path of execution inside one JVM process — create it with a Runnable, start it with start(), wait for it with join().

Process vs thread

ProcessThread
MemoryOwn address spaceShares the process heap; own stack
Creation costHeavy (fork, new address space)Lighter (~1 MB stack reserved for a platform thread)
CommunicationIPC: pipes, sockets, filesShared objects — fast, but needs synchronisation
Crash impactIsolatedAn uncaught error kills only that thread, but corrupted shared state affects all
In JavaProcessBuilder, one JVM = one processThread, executors, virtual threads

Creating and joining threads

Give each thread its own slice of the work, start() them, then join() before reading the results — join() both waits and guarantees you see everything the thread wrote.

Java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        long[] sums = new long[2];
        Thread t1 = new Thread(() -> sums[0] = sum(1, 50), "worker-1");
        Thread t2 = new Thread(() -> sums[1] = sum(51, 100), "worker-2");
        t1.start();                 // both now run concurrently with main
        t2.start();
        t1.join();                  // wait for each to finish
        t2.join();
        System.out.println(t1.getName() + " -> " + sums[0]);
        System.out.println(t2.getName() + " -> " + sums[1]);
        System.out.println("total = " + (sums[0] + sums[1]));
    }

    static long sum(int from, int to) {
        long s = 0;
        for (int i = from; i <= to; i++) s += i;
        return s;
    }
}
Output
worker-1 -> 1275
worker-2 -> 3775
total = 5050

Three ways to define the task

Java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread a = new Greeter();                       // 1. subclass Thread
        a.setName("greeter");
        a.start();
        a.join();

        Runnable task = new Runnable() {                // 2. implement Runnable
            @Override public void run() {
                System.out.println("2. Runnable on " + Thread.currentThread().getName());
            }
        };
        Thread b = new Thread(task, "runner");
        b.start();
        b.join();

        Thread c = new Thread(                          // 3. lambda (Runnable is functional)
            () -> System.out.println("3. lambda on " + Thread.currentThread().getName()), "lambda");
        c.start();
        c.join();
    }
}

class Greeter extends Thread {
    @Override public void run() {
        System.out.println("1. Thread subclass on " + getName());
    }
}
Output
1. Thread subclass on greeter
2. Runnable on runner
3. lambda on lambda

start() vs run()

Java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = () -> System.out.println("running in " + Thread.currentThread().getName());
        Thread t = new Thread(r, "worker");
        t.run();     // an ordinary method call — runs on main, no new thread
        t.start();   // creates the new call stack and calls run() there
        t.join();
        try {
            t.start();   // a Thread object can be started only once
        } catch (IllegalThreadStateException e) {
            System.out.println("second start(): " + e.getClass().getSimpleName());
        }
    }
}
Output
running in main
running in worker
second start(): IllegalThreadStateException

Thread lifecycle

NEW → RUNNABLE → BLOCKED / WAITING / TIMED_WAITING → RUNNABLE → TERMINATED

Thread.StateMeaningEntered by
NEWCreated, not yet startednew Thread(...)
RUNNABLERunning or ready to run (the JVM doesn't distinguish)start(); also while doing blocking I/O
BLOCKEDWaiting to acquire a monitor lockentering a synchronized block held by another thread
WAITINGWaiting indefinitely for another threadwait(), join(), LockSupport.park()
TIMED_WAITINGWaiting with a timeoutsleep(ms), wait(ms), join(ms)
TERMINATEDrun() returned or threwend of run()
Java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t = new Thread(() -> {
            synchronized (lock) { System.out.println("worker got the lock"); }
        });
        System.out.println("before start: " + t.getState());
        synchronized (lock) {                      // main holds the lock...
            t.start();
            while (t.getState() != Thread.State.BLOCKED) Thread.onSpinWait();
            System.out.println("waiting for lock: " + t.getState());
        }                                          // ...and releases it here
        t.join();
        System.out.println("after join: " + t.getState());
    }
}
Output
before start: NEW
waiting for lock: BLOCKED
worker got the lock
after join: TERMINATED

sleep, interrupt and daemon threads

Java has no safe way to kill a thread (stop() is deprecated and removed in practice). Instead you interrupt it — a polite request the thread must check for. Blocking calls (sleep, wait, join, BlockingQueue.take) respond by throwing InterruptedException and clearing the flag.

Java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread sleeper = new Thread(() -> {
            try {
                Thread.sleep(10_000);
                System.out.println("woke normally");
            } catch (InterruptedException e) {
                System.out.println("sleep interrupted, flag now " + Thread.currentThread().isInterrupted());
            }
        });
        sleeper.start();
        sleeper.interrupt();
        sleeper.join();

        Thread spinner = new Thread(() -> {
            long n = 0;
            while (!Thread.currentThread().isInterrupted()) n++;   // cooperative check
            System.out.println("spinner saw the interrupt and stopped");
        });
        spinner.start();
        spinner.interrupt();
        spinner.join();

        Thread daemon = new Thread(() -> {
            while (true) {
                try { Thread.sleep(1000); } catch (InterruptedException e) { return; }
            }
        });
        daemon.setDaemon(true);          // must be set before start()
        daemon.start();
        System.out.println("daemon? " + daemon.isDaemon() + " - JVM exits without waiting for it");
    }
}
Output
sleep interrupted, flag now false
spinner saw the interrupt and stopped
daemon? true - JVM exits without waiting for it
MethodWhat it does
Thread.sleep(ms)Pause the current thread; keeps any locks it holds
t.join() / t.join(ms)Wait until t terminates (happens-before: you see all its writes)
t.interrupt()Set t's interrupt flag; wakes it from sleep/wait/join
Thread.interrupted()Static: test and clear the current thread's flag
t.isInterrupted()Test the flag without clearing it
t.setDaemon(true)Background thread — the JVM exits when only daemons remain
Thread.yield()Hint to the scheduler; practically never needed

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

Runnable vs Callable?

How do you stop a thread?

Does sleep() release the lock?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report