Threads, Runnable and join
A thread is an independent path of execution inside one program. Every Java program starts with one, called main. Creating more lets the program do several things at once: serve many web requests, download files while the UI stays responsive, or split a big calculation across CPU cores. All threads in a program share the same heap, so they can see the same objects — which is both the power and the danger of this module.
The work a thread does is a Runnable: an interface with one method, void run(), so a lambda fits. Hand it to a Thread and call start(). Calling join() makes the current thread wait until that thread has finished.
public class Main {
public static void main(String[] args) throws InterruptedException {
Runnable job = () -> System.out.println("hello from " + Thread.currentThread().getName());
Thread worker = new Thread(job, "worker-1");
worker.start(); // runs job on a NEW thread
worker.join(); // main waits here until worker finishes
System.out.println("main continues on " + Thread.currentThread().getName());
job.run(); // a plain method call: runs on main
}
}hello from worker-1
main continues on main
hello from mainBecause of join(), the worker always prints first. Without it, the two lines could come out in either order.
Delete the worker.join() line and run it several times. The order of the first two lines is no longer guaranteed.
start() asks the JVM to create a new thread, which then calls run(). Calling run() yourself just executes the method on the current thread — no concurrency at all. It is the most common threading mistake in interviews and code reviews.Splitting work across threads
The safe pattern for a beginner: give each thread its own slot to write to, join them all, and only then read the results on main. No two threads touch the same variable, so there is nothing to race on, and join guarantees main sees everything the workers wrote.
public class Main {
public static void main(String[] args) throws InterruptedException {
long[] partial = new long[2];
Thread low = new Thread(() -> {
for (int i = 1; i <= 500; i++) partial[0] += i;
});
Thread high = new Thread(() -> {
for (int i = 501; i <= 1000; i++) partial[1] += i;
});
low.start();
high.start();
low.join();
high.join();
System.out.println(partial[0] + " + " + partial[1] + " = " + (partial[0] + partial[1]));
}
}125250 + 375250 = 500500Split the range 1..1000 across four threads instead of two, each writing to its own slot of a long[4].
You may also see threads written as class Worker extends Thread with run() overridden. It works, but passing a Runnable is preferred: your class stays free to extend something else, and the same task can later move to a thread pool unchanged.
unreported exception InterruptedException
public class Main {
public static void main(String[] args) {
Thread worker = new Thread(() -> System.out.println("working"));
worker.start();
worker.join();
System.out.println("done");
}
}Main.java:5: error: unreported exception InterruptedException; must be caught or declared to be thrown
worker.join();
^
1 error
error: compilation failedjoin(), Thread.sleep() and most other blocking calls can be woken early if another thread interrupts the waiting one. They signal that with the checked exception InterruptedException, and the compiler insists you deal with every checked exception.
In a small program, declare it on main: throws InterruptedException. Inside a Runnable (which cannot throw checked exceptions) catch it and restore the flag with Thread.currentThread().interrupt() so code further up can still see the thread was asked to stop.
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> System.out.println("working"));
worker.start();
worker.join();
System.out.println("done");
}
}IllegalThreadStateException: starting a thread twice
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> System.out.println("hello"));
t.start();
t.join();
t.start();
}
}hello
Exception in thread "main" java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Unknown Source)
at Main.main(Main.java:6)A Thread object is single-use. Once it has been started it moves through its lifecycle (NEW, RUNNABLE, TERMINATED) and can never go back to NEW, so a second start() is illegal even after it has finished.
Create a new Thread for each run, or — better — submit the Runnable to an ExecutorService as many times as you like.
public class Main {
public static void main(String[] args) throws InterruptedException {
Runnable hello = () -> System.out.println("hello");
for (int i = 0; i < 2; i++) {
Thread t = new Thread(hello);
t.start();
t.join();
}
}
}