Learn Java

Lesson 59Phase 7: Concurrency

Introduction to threads

Running more than one thing at once, the main thread, and creating a thread two ways.

By Updated 55 min

  • thread
  • main thread
  • Thread class
  • Runnable
  • start vs run
  • concurrency

Every program you have written so far has done one thing at a time. It starts at the top of main, runs down line by line, and stops at the bottom. That is all most programs need, until the day you want two things to happen at once: keep a window responsive while a long download runs, or download two songs side by side instead of one after the other. This lesson is about that second worker.

By the end of this lesson
  • Explain what a thread is and name the one every program already has.
  • Tell concurrency apart from parallelism, and say why a second thread helps even on a computer with one core.
  • Create a thread two ways: by extending Thread, and by handing a Runnable (often a lambda) to a Thread.
  • Tell start() apart from run(), and say why calling run() yourself starts no thread.
  • Name a thread, ask which thread is running a line, and say when a program with several threads ends.
  • Accept that the order two threads print in is not promised, and say why that matters.
From before
Making a thread uses moves you already know. You can extend a parent class and override one of its methods, implement an interface, and write a lambda for an interface that has one method. A few examples pause with a method that can throw a checked exception, which you learned to handle in exception handling.

You have always had one thread#

When you run a Java program, the JVM creates a thread for you and points it at main. A thread is one path of execution: a worker that walks through your code one statement at a time and keeps track of where it is. The worker the JVM makes for you is called the main thread, and so far it has done all of your work, alone.

Java can tell you which thread is running a line. Press Run.

WhoAmI.java
1public class Main {
2 public static void main(String[] args) {
3 String me = Thread.currentThread().getName();
4 System.out.println("Running on thread: " + me);
5 }
6}
Prints
›Running on thread: main

Thread.currentThread() hands back the thread that is running that line, and getName() reads its name. Only one thread exists, so the program prints Running on thread: main.

One worker does one thing at a time#

A music app wants to download two songs. Each download takes about two seconds, and for most of that time the computer is only waiting for data to arrive over the network. Here is the app with its one thread. Thread.sleep(2000) pauses the thread for 2000 milliseconds, which is two seconds, and stands in for a download. The visualizer narrates each pause instead of making you wait through it.

OneThread.java
1public class Main {
2 public static void main(String[] args) throws InterruptedException {
3 System.out.println("Start song 1");
4 Thread.sleep(2000); // stands in for a slow download
5 System.out.println("Done song 1");
6
7 System.out.println("Start song 2");
8 Thread.sleep(2000);
9 System.out.println("Done song 2");
10 }
11}
Prints
›Start song 1
›Done song 1
›Start song 2
›Done song 2

Thread.sleep can throw a checked exception, InterruptedException, so main declares it with throws. What that exception means comes up in the lesson on synchronization.

Song 2 cannot start until song 1 is done, even though the thread spends those two seconds doing nothing but waiting. On a real JVM the program takes about four seconds. In an app with a window, the same thread would usually also redraw the window and react to clicks, so the whole window would freeze until both downloads had finished.

A thread is not a program
Your whole running program is a process. One process can hold several threads, and they all share the same objects in memory. That sharing is what makes threads useful, because they can work on the same data, and it is also what makes them risky, as the lesson on synchronization shows. Picture one office (the process) with several workers (the threads) all reaching into the same set of drawers.

Concurrency and parallelism#

A second thread is a second worker inside the same program. Two words describe what the workers can do together, and people mix them up:

  • Concurrency means two or more tasks are in progress during the same stretch of time. They overlap: one starts before the other has finished, even if they never run at the same instant.
  • Parallelism means two tasks run at the very same instant, on different processor cores.

A coreis one processing unit inside the computer's CPU, and it runs one thread at any instant. Most computers and phones have several. This program asks how many processors Java can use:

Cores.java
read along
1public class Main {
2 public static void main(String[] args) {
3 int cores = Runtime.getRuntime().availableProcessors();
4 System.out.println("Processors available: " + cores);
5 }
6}

On the laptop used for this page it printed Processors available: 8. Yours may print 4, 12 or something else, so it is shown here as read-along.

Programs usually have more threads than the computer has cores, so the operating system's scheduler makes them take turns. It runs a thread for a few milliseconds, pauses it, runs another, and switches so quickly that they all seem to run at once. A thread that is waiting, for a download or for a timer, does not need a turn at all. So even on a single core, a second thread can get work done while the first one waits. That is concurrency without parallelism, and it is what most programs use threads for.

Key idea

Concurrency is tasks overlapping in time. Parallelism is tasks running at the same instant on different cores. Threads give you concurrency on any computer, and parallelism when there are cores to spare.

Quick check
A laptop has a single core. A program starts two threads, and each one downloads a file. What happens?

Way one: extend the Thread class#

Java has a class called Thread, and each Threadobject can run as a separate worker. The job the worker does lives in the thread's run() method, which on a plain Thread does nothing. So the first way to make your own thread is the inheritance you already know: write a class that extends Thread and override run() with the work you want done.

Greeter.java
read along
1public class Main {
2 public static void main(String[] args) {
3 Greeter g = new Greeter();
4 g.start();
5
6 System.out.println("main says hello");
7 }
8}
9
10class Greeter extends Thread {
11 public void run() {
12 System.out.println("worker says hello");
13 }
14}
Output
main says hello
worker says hello

Read it slowly. Greeter is an ordinary class that happens to extend Thread, and its run() method holds one print. In main the program builds a Greeter object the usual way, then calls g.start(). That one call asks the JVM for a brand new thread, a second worker, and the new worker runs the run() method. Meanwhile the main thread does not wait around. It carries straight on to its own println. For a moment two workers are running, each printing one line.

Key idea

start() launches a new thread, and that new thread runs run(). The thread that called start() keeps going without waiting.

Why the output is not nailed down
Once two threads are running, nothing promises which one prints first. Starting a thread takes the JVM a moment, so main usually wins, but not always: another run of this same program printed worker says hello first and main says hello second. Never write code that depends on which line comes first.

Way two: hand a Runnable to a Thread#

Extending Thread works, but it spends your one chance at inheritance. A Java class can extend only one parent, so if Greeter already needed to extend some other class, it could not also extend Thread. The second way avoids that. Instead of being a thread, your class is only the job, and you hand the job to a thread.

A job is described by an interface called Runnable. It lives in java.lang, so it needs no import, and it has exactly one method, run(). You write a class that implements Runnable and put the work in run(). Then you create a Thread and pass your job to its constructor. The thread keeps the job and runs it once it is started.

Task.java
read along
1public class Main {
2 public static void main(String[] args) {
3 Task job = new Task();
4 Thread t = new Thread(job);
5 t.start();
6
7 System.out.println("main is done");
8 }
9}
10
11class Task implements Runnable {
12 public void run() {
13 System.out.println("task is running");
14 }
15}
Output
main is done
task is running

Task is not a thread. It is a bundle of work: an object whose run() says what to do. The program makes a real Thread and passes job to its constructor, so the thread knows which job to run. Calling t.start() launches the new worker, and the new worker runs job's run(). The two lines race just as before: in one batch of 100 runs, this program printed main is done first 97 times and task is running first 3 times.

extends Thread
implements Runnable
Your class is a thread.
Your class is the job, and a separate Thread runs it.
Uses up your one parent class.
Leaves the class free to extend another class.
Start it with g.start().
Wrap it, then start: new Thread(job).start().
Overrides run() from the Thread class.
Implements run() from the Runnable interface.
Fine for a quick one-off worker.
The usual choice in real code.
When you are unsure, reach for Runnable
Both ways work, but real code usually uses Runnable. It leaves your class free to extend something else, and it keeps the job apart from the thread that runs it, so the same job can be given to a different runner later. In thread pools, executors and futures you hand jobs like this to a pool of threads that are already running.

The short form: a lambda#

Runnable has exactly one abstract method, so it is a functional interface, and you can write a job as a lambda, as you did in enums and lambdas. The lambda becomes the body of run(). It takes no parameters and returns nothing, so it starts with empty parentheses: () -> ....

TaskLambda.java
read along
1public class Main {
2 public static void main(String[] args) {
3 Thread t = new Thread(() -> System.out.println("task is running"));
4 t.start();
5
6 System.out.println("main is done");
7 }
8}
Output
main is done
task is running

One line now does the work of the whole Task class. The lambda is a Runnable object, built on the spot and handed straight to the Thread. The output has the same two lines in the same uncertain order. For a short job, this is the form you will see most often.

Quick check
Given Runnable job = () -> System.out.println("saving");, which line runs the job on a new thread?

start() versus run(): the beginner trap#

Your job lives in a method called run(), so it is tempting to start a worker by calling run(). Before reading on, decide what this program prints. It makes two jobs and calls their run() methods directly.

Predict the outputLaundry.java
1public class Main {
2 public static void main(String[] args) {
3 Runnable wash = () -> System.out.println("wash on " + Thread.currentThread().getName());
4 Runnable dry = () -> System.out.println("dry on " + Thread.currentThread().getName());
5
6 dry.run();
7 wash.run();
8 System.out.println("main is done");
9 }
10}

Which thread runs each job, and in what order do the lines appear?

It prints dry on main, wash on main, then main is done. Making a Runnable does not run it, and calling its run() is an ordinary method call: the job runs right there, on the thread that called it, before the next line starts. A Thread object behaves the same way when you call its run():

Trap.java
read along
1public class Main {
2 public static void main(String[] args) {
3 Runnable job = () -> System.out.println("job runs on " + Thread.currentThread().getName());
4 Thread worker = new Thread(job, "worker");
5
6 worker.run(); // an ordinary method call
7 worker.start(); // a new thread named worker runs the job
8 }
9}
Output
job runs on main
job runs on worker

The second argument in new Thread(job, "worker") gives the thread a name. Both lines of output come from the same job. worker.run() ran it on the main thread, like any method call. worker.start() made a new thread called worker, and that thread ran it. This output is the same on every run, because run() has finished before start() is even called.

Key idea

Calling run() yourself is an ordinary method call. The job runs on the current thread, in order, and no new thread is born. Only start() creates a new thread.

The compiler will not catch it
worker.run()is a legal method call, so the program compiles, runs and prints. It just quietly does the wrong thing: your “background” work happens on the main thread while everything else waits, and you gain nothing. If a threaded program behaves as if it had only one thread, look for a run() that should be a start().

Every thread has a name#

A thread you do not name gets one automatically: Thread-0 for the first, Thread-1 for the next, and so on. Names show up when you print Thread.currentThread().getName(), and in the first line of an error that happens on that thread, so a good name makes a problem easier to trace.

Names.java
read along
1public class Main {
2 public static void main(String[] args) {
3 Runnable job = () -> System.out.println("hello");
4
5 Thread first = new Thread(job);
6 Thread second = new Thread(job);
7 Thread named = new Thread(job, "downloader");
8
9 System.out.println(first.getName());
10 System.out.println(second.getName());
11 System.out.println(named.getName());
12 }
13}
Output
Thread-0
Thread-1
downloader

None of these threads is ever started, so hello never prints. A Thread object exists as soon as new runs, and it only becomes a running worker when you call start(). You can also rename a thread with setName("...").

Calling run() on purpose#

Calling run() yourself has one honest use: checking that a job does the right thing. The job runs on the main thread, in order, so its output is the same every time. The exercise below works that way, because the visualizer can run a job on the main thread but cannot start a new one.

ExerciseCountdown.java
Finish the Countdown job so that its run() prints the numbers from from down to 1, one per line, and then Liftoff!. main tests two countdowns by calling run() directly.

Once the job works, a real program would hand it to a thread with new Thread(new Countdown(3)).start(), and the countdown would run on its own while main carried on.

Two downloads at once#

Back to the music app. This time each download is a Runnablejob that keeps its song's name in a field, and main gives each job its own thread.

TwoDownloads.java
read along
1public class Main {
2 public static void main(String[] args) {
3 Thread one = new Thread(new Download("song 1"));
4 Thread two = new Thread(new Download("song 2"));
5
6 one.start();
7 two.start();
8 System.out.println("main is finished");
9 }
10}
11
12class Download implements Runnable {
13 private final String song;
14
15 Download(String song) {
16 this.song = song;
17 }
18
19 public void run() {
20 System.out.println("Start " + song);
21 try {
22 Thread.sleep(2000); // stands in for a slow download
23 } catch (InterruptedException e) {
24 return;
25 }
26 System.out.println("Done " + song);
27 }
28}
Output
main is finished
Start song 1
Start song 2
Done song 1
Done song 2

One detail is new. Here the sleep sits inside a try, because run() is not allowed to declare throws InterruptedException. The run() in Runnable declares no checked exceptions, and a method that implements it may not add any. If the sleep is ever interrupted, the return ends the job early.

Now look at the output. Both downloads start before either one finishes, so they overlap, and the whole program takes about two seconds instead of four. The two Start lines often swap places from one run to the next, and so do the two Done lines.

The first line is a surprise too. main is finished printed before either download had started, yet the program kept going for two more seconds. Java does not end a program when main returns. It waits until every thread you started has finished.

Key idea

A Java program ends when its last thread ends, not when main returns.

The order is not promised#

To see the scheduler at work, start three workers in a loop, numbered 1, 2 and 3, and let main print a line too.

Race.java
read along
1public class Main {
2 public static void main(String[] args) {
3 for (int i = 1; i <= 3; i++) {
4 Worker w = new Worker(i);
5 w.start();
6 }
7 System.out.println("main: hi");
8 }
9}
10
11class Worker extends Thread {
12 private final int id;
13
14 Worker(int id) {
15 this.id = id;
16 }
17
18 public void run() {
19 System.out.println("worker " + id + ": hi");
20 }
21}
Output
main: hi
worker 2: hi
worker 3: hi
worker 1: hi

The workers were started in the order 1, 2, 3, yet this run printed worker 2 first and worker 1 last. Here it is next to another run of the same code:

One run
Another run
main: hi
main: hi
worker 2: hi
worker 1: hi
worker 3: hi
worker 3: hi
worker 1: hi
worker 2: hi

Over 150 runs, the three workers came out in all six possible orders, each about as often as the others. Once threads are running, the order of their lines is decided moment to moment by the scheduler, and that depends on everything else the computer is doing. Every line appears; which comes first is down to timing. So treat any thread output in this course as one real run out of many possible ones. If a program only works when its lines come out in one particular order, it has a bug.

Quick check
In the three-worker program, which statement holds for every run?
Where threads go next
Printing in a surprising order is harmless. But threads share the same objects, and when two of them change the same number at the same moment, the total can come out wrong, in a way that is hard to reproduce because it depends on timing. Thread creation and synchronization shows that happening, and how to stop it. After that, the course moves on from starting threads one at a time. Most new code hands its jobs to a pool of ready threads, the subject of thread pools, executors and futures, or uses the very cheap threads added in Java 21, the subject of virtual threads.

Try it

Answer each one in your head or on paper first, then open the answer.

1
Before you create any thread of your own, how many threads is your program running, and what is that thread called?
2
You wrote class Job extends Thread with a run() method, built Job j = new Job(), and then called j.run() instead of j.start(). Was a new thread created? Which thread ran the job?
3
Name the two ways to make a thread, give the short form of the second, and give one practical reason to prefer the second way.
4
Two threads each print one line. On Monday the program prints them in one order; on Tuesday, with no change to the code, it prints them in the other order. Is the program broken?
5
The last line of main has run, but a thread that main started is still in the middle of a two-second sleep. Does the program end now?
6
In the Task example, which object goes inside new Thread(...), and what does the thread do with it?

Practice

Work through these on your own. Each one builds on this page.

1
On your own computer, save the three-worker Race program as Main.java and run it five times in a row with java Main.java. Write down the order of the four lines each time. Did any two runs match? Write one sentence on what that tells you about thread order.
2
Rewrite the Greeter example (the extends Thread one) twice: first with a class that implements Runnable, then with a lambda. Keep the printed lines the same.
3
Take any of the thread examples and change start() to run(). Before running it, write down what you expect to see and why. Then run it, with a line inside the job that prints Thread.currentThread().getName(), to confirm which thread did the work.
4
Give Download a second field for how long the download takes in milliseconds, and start song 1 with 3000 and song 2 with 1000. Before running it, write down the order of the five lines and roughly how long the whole program will take. Then run it and compare.
5
In one short paragraph, explain why a program that downloads a large file might want a second thread, even on a computer with a single core. Use the words concurrency and parallelism correctly.

Further reading#

Spotted a mistake, or a part that did not make sense? Report it by email