Lesson 59Phase 7: Concurrency
Introduction to threads
Running more than one thing at once, the main thread, and creating a thread two ways.
By BasithUpdated 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.
- 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 aRunnable(often a lambda) to aThread. - Tell
start()apart fromrun(), and say why callingrun()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.
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.
public class Main { public static void main(String[] args) { String me = Thread.currentThread().getName(); System.out.println("Running on thread: " + me); }}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.
public class Main { public static void main(String[] args) throws InterruptedException { System.out.println("Start song 1"); Thread.sleep(2000); // stands in for a slow download System.out.println("Done song 1"); System.out.println("Start song 2"); Thread.sleep(2000); System.out.println("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.
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:
public class Main { public static void main(String[] args) { int cores = Runtime.getRuntime().availableProcessors(); System.out.println("Processors available: " + cores); }}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 ideaConcurrency 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.
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.
public class Main { public static void main(String[] args) { Greeter g = new Greeter(); g.start(); System.out.println("main says hello"); }} class Greeter extends Thread { public void run() { System.out.println("worker says hello"); }}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 runsrun(). The thread that calledstart()keeps going without waiting.
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.
public class Main { public static void main(String[] args) { Task job = new Task(); Thread t = new Thread(job); t.start(); System.out.println("main is done"); }} class Task implements Runnable { public void run() { System.out.println("task is running"); }}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.
Thread runs it.g.start().new Thread(job).start().run() from the Thread class.run() from the Runnable interface.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: () -> ....
public class Main { public static void main(String[] args) { Thread t = new Thread(() -> System.out.println("task is running")); t.start(); System.out.println("main is done"); }}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.
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.
public class Main { public static void main(String[] args) { Runnable wash = () -> System.out.println("wash on " + Thread.currentThread().getName()); Runnable dry = () -> System.out.println("dry on " + Thread.currentThread().getName()); dry.run(); wash.run(); System.out.println("main is done"); }}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():
public class Main { public static void main(String[] args) { Runnable job = () -> System.out.println("job runs on " + Thread.currentThread().getName()); Thread worker = new Thread(job, "worker"); worker.run(); // an ordinary method call worker.start(); // a new thread named worker runs the job }}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 ideaCalling
run()yourself is an ordinary method call. The job runs on the current thread, in order, and no new thread is born. Onlystart()creates a new thread.
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.
public class Main { public static void main(String[] args) { Runnable job = () -> System.out.println("hello"); Thread first = new Thread(job); Thread second = new Thread(job); Thread named = new Thread(job, "downloader"); System.out.println(first.getName()); System.out.println(second.getName()); System.out.println(named.getName()); }}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.
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.
public class Main { public static void main(String[] args) { Thread one = new Thread(new Download("song 1")); Thread two = new Thread(new Download("song 2")); one.start(); two.start(); System.out.println("main is finished"); }} class Download implements Runnable { private final String song; Download(String song) { this.song = song; } public void run() { System.out.println("Start " + song); try { Thread.sleep(2000); // stands in for a slow download } catch (InterruptedException e) { return; } System.out.println("Done " + song); }}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 ideaA Java program ends when its last thread ends, not when
mainreturns.
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.
public class Main { public static void main(String[] args) { for (int i = 1; i <= 3; i++) { Worker w = new Worker(i); w.start(); } System.out.println("main: hi"); }} class Worker extends Thread { private final int id; Worker(int id) { this.id = id; } public void run() { System.out.println("worker " + id + ": hi"); }}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:
main: himain: hiworker 2: hiworker 1: hiworker 3: hiworker 3: hiworker 1: hiworker 2: hiOver 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.
Try it
Answer each one in your head or on paper first, then open the answer.
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?main has run, but a thread that main started is still in the middle of a two-second sleep. Does the program end now?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.
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.Greeter example (the extends Thread one) twice: first with a class that implements Runnable, then with a lambda. Keep the printed lines the same.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.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.Further reading#
Spotted a mistake, or a part that did not make sense? Report it by email