Threads
Problem
Three reports, one at a time
Thread.sleep(ms) stops the program for that many milliseconds. The pause can be interrupted from outside, and then it throws the checked InterruptedException, so download catches it.static void download(String file, int ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { return; } System.out.println("Downloaded " + file);}
public static void main(String[] args) { download("march.pdf", 600); download("april.pdf", 200); download("may.pdf", 400); System.out.println("Done");}Downloaded march.pdfDownloaded april.pdfDownloaded may.pdfDoneThe idea
Process and thread
When you start the program, the operating system gives it a process: its own memory, its own open files. Inside the process, one or more threads run. A thread is a sequence of commands that run one after another. So far, each of your programs had one thread: the one that runs main.
The threads in one process share their memory: they see the same objects. That makes them handy (they can easily pass data to each other) and dangerous (they can mess each other up, as you will see in a moment).
- Parallel means really at the same time, on different cores of the processor.
- Concurrent means on one core, taking turns so fast that it looks like the same time.
For the programmer the difference rarely matters: in both cases you do not know which thread will go on when.
More precisely, if you are curious
- The Chrome browser starts a separate process for each tab. That is why one frozen site does not stop the others, and also why the browser uses a lot of memory.
- Every program that does several things at once uses threads: a game plays the sound in one thread and runs the logic in another; a web server serves each client in its own thread.
- The price: each thread has its own stack, and the memory for it is set aside separately. Thousands of threads cost a lot. That is why real programs use a thread pool (the last idea in the lesson).
See it
The first thread
The Thread class is a thread. You pass its constructor what to do: an object of the Runnable interface, which has one method, run(). The shortest way is with a lambda:
Thread worker = new Thread(() -> download("april.pdf", 200));worker.start();System.out.println("main does not wait");worker.join();System.out.println("The report is downloaded");main does not waitDownloaded april.pdfThe report is downloadedstart()starts the new thread and returns right away.maingoes on without waiting, so "main does not wait" is printed first.join()makesmainwait for the thread to finish.
download is the method from the first card. It is a good thing that it catches InterruptedException itself: run() in Runnable has no throws, so the code in the thread cannot let the checked exception out. join() throws it too, so from here on main is public static void main(String[] args) throws InterruptedException.
More precisely, if you are curious
class ReportThread extends Thread { @Override public void run() { System.out.println("1. a subclass of Thread"); }}
class ReportJob implements Runnable { @Override public void run() { System.out.println("2. a class that implements Runnable"); }}
public static void main(String[] args) throws InterruptedException { Thread a = new ReportThread(); Thread b = new Thread(new ReportJob()); Thread c = new Thread(new Runnable() { @Override public void run() { System.out.println("3. an anonymous class"); } }); Thread d = new Thread(() -> System.out.println("4. a lambda")); for (Thread t : new Thread[] { a, b, c, d }) { t.start(); t.join(); }}1. a subclass of Thread2. a class that implements Runnable3. an anonymous class4. a lambdaThread is the oldest way. It is more flexible to keep the task (Runnable) apart from the thread: that way the same task can be given to a thread pool, and your class stays free to extend another class.See it
Three at once
long start = System.currentTimeMillis();Thread march = new Thread(() -> download("march.pdf", 600));Thread april = new Thread(() -> download("april.pdf", 200));Thread may = new Thread(() -> download("may.pdf", 400));march.start();april.start();may.start();march.join();april.join();may.join();long ms = System.currentTimeMillis() - start;System.out.println("Done in under a second: " + (ms < 1000));Downloaded april.pdfDownloaded may.pdfDownloaded march.pdfDone in under a second: trueThe threads start in the order March, April, May, but they finish in the order of the pauses: April (0.2 s), May (0.4 s), March (0.6 s). The whole wait is as long as the longest one, 0.6 seconds instead of 1.2.
The operating system decides the order in which the threads reachprintln. Here the pauses are different enough, so the order is the same. If they were equal, it could be different on every run.Try it
start or run
What will the program print? (the second argument of Thread is the name of the thread)
Thread t = new Thread(() -> System.out.println("Running in: " + Thread.currentThread().getName()), "downloader");t.run();t.start();t.join();t.run() is an ordinary method call: the lambda runs right away, in the thread of main. There is no new thread. Only t.start() starts the thread "downloader", which runs the same lambda. Writing run() instead of start() is a common mistake: the program works, only nothing runs in parallel.The idea
The life of a thread
getState() tells you which one it is in:Thread t = new Thread(() -> download("may.pdf", 500));System.out.println(t.getState());t.start();Thread.sleep(100);System.out.println(t.getState());t.join();System.out.println(t.getState());NEWTIMED_WAITINGDownloaded may.pdfTERMINATED| State | When |
|---|---|
NEW |
created, not started with start() yet |
RUNNABLE |
running or ready to run |
BLOCKED, WAITING, TIMED_WAITING |
waiting: for a lock, for another thread (join), or for a set time (sleep) |
TERMINATED |
run() has finished |
A thread can be started only once. A second start() on the same thread throws IllegalThreadStateException.
More precisely, if you are curious
More Thread methods:
interrupt()asks the thread to stop. If it is sleeping or waiting,sleep/jointhrowsInterruptedException. That is why we catch it indownload.getName(),threadId()(earliergetId()): the name and the number of the thread.Thread.currentThread()gives you the thread the code is running in.Thread.yield()hints that the thread can give the processor to another one. You rarely need it.
Problem
The lost visits
increment() 100,000 times:class Counter { private int count = 0;
public void increment() { count++; }
public int getCount() { return count; }}
public static void main(String[] args) throws InterruptedException { Counter visits = new Counter(); Runnable work = () -> { for (int i = 0; i < 100_000; i++) { visits.increment(); } }; Thread first = new Thread(work); Thread second = new Thread(work); first.start(); second.start(); first.join(); second.join(); System.out.println("Visits: " + visits.getCount());}You expect 200,000. Five runs:
Visits: 150329Visits: 138149Visits: 168862Visits: 165672Visits: 157704Less every time, and different every time. The reason is that count++ is not one step, but three:
int value = count; // 1. readvalue = value + 1; // 2. add 1count = value; // 3. writeclass Counter { private int count = 5;
public void increment() { count++; // read, add 1, write }
public synchronized void safeIncrement() { count++; }
public synchronized int getCount() { return count; }}Step by step, in words
- count is 5. Threads A and B call increment() at the same time.
- A reads count: 5.
- B reads count before A has written: also 5.
- A adds 1 in its own copy and writes 6.
- B writes 6 too. Two calls, and count is 6, not 7: one of the additions is lost.
- Once more, now with synchronized: A takes the lock, reads 5 and writes 6.
- B reaches the method and waits until A releases the lock.
- B takes the lock, reads 6 and writes 7: nothing is lost.
When both threads read the same value before either of them has written, one of the additions is lost. This is called a race condition. Whether it happens depends on when the operating system switches the threads, so the result is different on every run.
The idea
synchronized: one at a time
synchronized in front of the method does exactly that:class Counter { private int count = 0;
public synchronized void increment() { count++; }
public synchronized int getCount() { return count; }}Visits: 200000Every object has a lock. The thread takes it before it enters a synchronized method and releases it when it leaves. The other thread that wants the same object waits. Now the result is 200,000 on every run. getCount() is synchronized too: that way the thread that reads sees the last value written.
If you need to lock only part of a method, there is a synchronized block with an object for the lock. For a counter there is also a ready-made class: AtomicInteger (from java.util.concurrent.atomic), whose incrementAndGet() does the three steps at once, without a lock:
class Counter { private int count = 0; private final Object lock = new Object();
public void increment() { synchronized (lock) { count++; } }
public int getCount() { synchronized (lock) { return count; } }}class Counter { private final AtomicInteger count = new AtomicInteger();
public void increment() { count.incrementAndGet(); }
public int getCount() { return count.get(); }}Visits: 200000More precisely, if you are curious
wait()andnotifyAll(): a thread in a synchronized method can release the lock and go to sleep until another thread wakes it up. That way the kitchen waits for an order, and the checkout waits for the kitchen to take the previous one (the "producer and consumer" problem):
class OrderBox { private String order;
public synchronized void put(String newOrder) throws InterruptedException { while (order != null) { wait(); } order = newOrder; notifyAll(); }
public synchronized String take() throws InterruptedException { while (order == null) { wait(); } String result = order; order = null; notifyAll(); return result; }}
public static void main(String[] args) throws InterruptedException { OrderBox box = new OrderBox(); Thread kitchen = new Thread(() -> { try { for (int i = 0; i < 3; i++) { System.out.println("The kitchen took " + box.take()); } } catch (InterruptedException e) { return; } }); kitchen.start(); box.put("pizza"); box.put("salad"); box.put("soup"); kitchen.join();}The kitchen took pizzaThe kitchen took saladThe kitchen took soupwait() is in a while, not in an if: the thread can wake up for no reason, so it checks the condition again.
- Deadlock: two threads wait for each other forever. The office holds the printer and waits for the scanner, and the front desk holds the scanner and waits for the printer (
pauseissleepwith the exception caught):
Object printer = new Object();Object scanner = new Object();
Thread office = new Thread(() -> { synchronized (printer) { pause(100); synchronized (scanner) { System.out.println("The office is copying"); } }});Thread reception = new Thread(() -> { synchronized (scanner) { pause(100); synchronized (printer) { System.out.println("The front desk is copying"); } }});The program hangs and never prints anything. The way to avoid it is for all the threads to take the locks in the same order.
- Priorities:
setPriorityfrom 1 to 10 hints how important the thread is. The operating system may ignore it, so do not rely on priorities for the program to be correct:
Thread report = new Thread(() -> download("annual.pdf", 100));report.setPriority(Thread.MIN_PRIORITY); // 1; NORM_PRIORITY is 5, MAX_PRIORITY is 10System.out.println(report.getName() + ", priority " + report.getPriority());Try it
Two ATMs
Pick the line with the bug.
withdraw is not synchronized. The two threads check balance >= amount at the same time, both see 100 and both withdraw. The check and the withdrawal have to be one whole: public synchronized void withdraw(int amount) (and synchronized for getBalance). Then the second ATM waits for the first one, sees €20 and refuses. The balance stays €20.The idea
Thread pool and Future
ExecutorService (from java.util.concurrent):static int sizeOf(String file, int ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { return -1; } return file.length() * 100;}String[] files = { "march.pdf", "april.pdf", "may.pdf", "june.pdf" };ExecutorService pool = Executors.newFixedThreadPool(2);List<Future<Integer>> sizes = new ArrayList<>();for (String file : files) { sizes.add(pool.submit(() -> sizeOf(file, 300)));}for (int i = 0; i < files.length; i++) { System.out.println(files[i] + ": " + sizes.get(i).get() + " KB");}pool.shutdown();march.pdf: 900 KBapril.pdf: 900 KBmay.pdf: 700 KBjune.pdf: 800 KBExecutors.newFixedThreadPool(2)makes a pool of 2 threads. The four tasks take turns in them.submittakes a task that returns a result and right away gives you aFuture: a promise that the result will come.get()waits for the task to finish and returns the result.shutdown()tells the pool not to accept new tasks and to stop the threads when they are done. Without it the program does not end: the threads of the pool wait for more work.
main here has throws Exception, because get() also throws ExecutionException if the task threw an exception.
More precisely, if you are curious
execute. Then you wait for all of them with awaitTermination after shutdown:ExecutorService pool = Executors.newFixedThreadPool(2);for (int i = 1; i <= 5; i++) { String file = "report-" + i + ".pdf"; pool.execute(() -> download(file, 100));}pool.shutdown();pool.awaitTermination(1, TimeUnit.MINUTES);System.out.println("All done.");Downloaded report-2.pdfDownloaded report-1.pdfDownloaded report-3.pdfDownloaded report-4.pdfDownloaded report-5.pdfAll done.while (!executor.isTerminated()) { }. That waits too, but it spins the processor for nothing. awaitTermination sleeps until the pool is done.Try it
Which result comes first?
"archive.zip" takes 0.6 s to download, and "note.txt" takes 0.1 s. What will the program print?
ExecutorService pool = Executors.newFixedThreadPool(2);Future<Integer> big = pool.submit(() -> sizeOf("archive.zip", 600));Future<Integer> small = pool.submit(() -> sizeOf("note.txt", 100));System.out.println("Big: " + big.get());System.out.println("Small: " + small.get());pool.shutdown();The small file really is ready first. But big.get() is the first line, and it waits for the big one. When that one comes, small.get() returns right away, because the result is already there. The order of the output is the order of the get() calls, so it is the same on every run.
Exercise
Programs with threads
Solve these in your editor. main can have throws InterruptedException (or throws Exception for Future.get()).
1. Parallel sum. The array sales has 1,000,000 sales, sales[i] = i % 100. Split it into 4 equal parts and sum each one in its own thread. Each thread writes to its own place in the array long[] partial, so you do not need synchronization. Wait for them with join and add up the parts:
Parts: [12375000, 12375000, 12375000, 12375000]Total: 495000002. Downloading with a pool. Download six reports (march.pdf, april.pdf, may.pdf, june.pdf, july.pdf, august.pdf) with a pool of 3 threads. Use sizeOf(file, 300) from the pool card. Print the sizes in the order of the files, the total, and whether it all took under 1.5 seconds (one after another would be 1.8 s):
march.pdf: 900 KBapril.pdf: 900 KBmay.pdf: 700 KBjune.pdf: 800 KBjuly.pdf: 800 KBaugust.pdf: 1000 KBTotal: 5100 KBUnder 1.5 seconds: true3. A counter without losses. Four threads, each with 50,000 visits, increase a shared counter. Write the counter so that the result is always 200,000. First try it without synchronization and see how much is lost, then fix it once with synchronized and once with AtomicInteger.
Visits: 200000More tasks
- A test for the counter. Write a JUnit test that starts 8 threads with 10,000 increases each and checks for 80,000 with
assertEquals. Run it against the unsynchronized counter a few times. A test that fails only sometimes is the typical sign of a race condition. - The deadlock. Run the example with the printer and the scanner from "More precisely" and watch it hang. Fix it so that both threads take the printer first.
Test