Threads
A thread is a separate sequence of instructions inside a program. Normally a program runs code line by line in one thread — the main one, called main. If you start several threads, they run at the same time: while one waits for a network reply, another processes data. Don't confuse them with streams (the Stream API) — they are different things.
The first thread
A thread is created with the Thread class by giving it a task — a lambda with no parameters. The start() method starts the thread, and join() makes the current thread wait until it finishes:
void main() throws InterruptedException {
Thread worker = new Thread(() -> IO.println("Hello from another thread"));
IO.println("Starting");
worker.start();
worker.join();
IO.println("Done");
}Output:
Starting
Hello from another thread
DoneThanks to join() the "Done" line is guaranteed to be printed last. join and other waiting methods may throw the checked InterruptedException — if the thread is asked to stop waiting — so main declares throws.
Order is not guaranteed
Threads run independently, and the operating system decides which one gets the processor. That's why the output order of two threads changes from run to run:
void count(String name) {
for (int i = 1; i <= 3; i++) {
IO.println(name + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
return;
}
}
}
void main() throws InterruptedException {
Thread a = new Thread(() -> count("A"));
Thread b = new Thread(() -> count("B"));
a.start();
b.start();
a.join();
b.join();
}Output (one possible run — your order may differ):
B1
A1
B2
A2
A3
B3Within one thread the order is preserved: A1 always comes before A2. But how the A and B lines get mixed can't be predicted.
Pauses
Thread.sleep pauses the current thread for a given time. It's handy for imitating slow work — a server request or reading a big file:
void main() throws InterruptedException {
IO.println("Loading...");
Thread.sleep(Duration.ofMillis(500));
IO.println("Loaded");
}Output:
Loading...
LoadedThread names
Every thread has a name. Thread.currentThread().getName() returns it, so you can find out which thread the code is running in. A convenient way to set a name is the Thread.ofPlatform() builder:
void main() throws InterruptedException {
Thread worker = Thread.ofPlatform()
.name("loader")
.start(() -> IO.println("Running in: " + Thread.currentThread().getName()));
worker.join();
IO.println("Running in: " + Thread.currentThread().getName());
}Output:
Running in: loader
Running in: mainThe builder's start method both creates and starts the thread.
A result from a thread
The lambda in a Thread returns nothing. A simple way to collect results is to give each thread its own array cell and read the array after join:
void main() throws InterruptedException {
int[] sums = new int[2];
Thread first = new Thread(() -> sums[0] = IntStream.rangeClosed(1, 500).sum());
Thread second = new Thread(() -> sums[1] = IntStream.rangeClosed(501, 1000).sum());
first.start();
second.start();
first.join();
second.join();
IO.println("Sum from 1 to 1000: " + (sums[0] + sums[1]));
}Output:
Sum from 1 to 1000: 500500The threads write to different cells and don't get in each other's way, and join guarantees both have finished by the time we read. Thread pools make getting results easier — they're the topic of the next lesson.
Common mistake
If you call run() instead of start(), no new thread is created: the task simply runs in the current thread, like an ordinary method:
void main() {
Thread worker = new Thread(() -> IO.println("Running in " + Thread.currentThread().getName()));
worker.run();
}Output:
Running in mainThere's no error, but there's no parallelism either. To run code in a separate thread, always call start().