Threads
Goal
- Understand what threads are and what they are used for.
- Be able to start threads, allow them to end, and join them.
- Make a thread a daemon.
Concepts
- central processing unit (CPU)
- concurrent
- daemon
- in parallel
- interrupt
- interrupt status
- join (thread)
- multitask
- multi-threaded
- process
- sleep
- thread
Language
volatile
Library
java.lang.Runnable
java.lang.Runnable.run()
java.lang.System.currentTimetMilliseconds()
java.lang.Thread
java.lang.Thread.currentThread()
java.lang.Thread.getName()
java.lang.Thread.getPriority()
java.lang.Thread.getState()
java.lang.Thread.interrupt()
java.lang.Thread.interrupted()
java.lang.Thread.isInterrupted()
java.lang.Thread.join()
java.lang.Thread.setDaemon(boolean on)
java.lang.Thread.setPriority(int newPriority)
java.lang.Thread.setName(String name)
java.lang.Thread.sleep(long milliseconds)
java.lang.Thread.start()
java.lang.Thread.State
java.util.concurrent.TimeUnit
Lesson
The central processing unit (CPU) of your computer, even if it is several years old, probably has several cores
that allow it to perform several tasks literally at the same time. Even on single-core machines, modern operating systems provide the ability to multitask, allowing several programs be open and running simultaneously. Each program is placed inside a process with its own allocation of memory and other resources.
If possible each core of the CPU is assigned to the processor so that it can truly run simultaneous or in parallel with the other processes. If your computer has multiple processors, even better—each processor will be assigned a separate process. Even if it is not possible to run the processes literally at the same instance, the operating system will switch between the processes very quickly to provide the illusion that they are operating at the same time. Either way, each process will complete its tasks concurrently with the other processes—independently but during the same space of time.
But multitasking is beneficial far beyond simply performing two tasks at once. It turns out that many programs spend much of their time waiting—on the user to enter something, or for something to be downloaded from the Internet. While a process is waiting, the operating system can suspend that process and let another one run, waking up the first process when data is ready. This is much more efficient than holding up the entire system just waiting for something to happen outside the computer.
Threads
All the programs you've written during this course have been one continuous sequence of logic. There have been multiple classes using many methods, this is true, but control was handed sequentially from class to class, from method to method. While one part of your program was waiting to write to the disk, your entire program had to wait until this operation was finished. So far you have not provided any way for another part of your program to do any work simultaneously.
This single path of execution within the program is called a thread of execution. A process will always have at least one thread, but it may have more. All the threads in a process share memory and resources; they could be considered lightweight processes
.
The Java class that represents a thread is java.lang.Thread
. You can get the object representing the current thread of execution by calling Thread.currentThread()
. Each thread has several properties available, including Thread.getName()
, Thread.getPriority()
, and Thread.getState()
:
Thread State
At any time Thread
instance is in one of several possible states, represented by the Thread.State
enum. A newly created thread start out as NEW
, and after it has been started it is RUNNABLE
. When a thread has ended it is in the TERMINATED
state. Before being terminated a thread may be BLOCKED
or WAITING
/TIMED_WAITING
, as you will learn later.
Thread Priority
While the thread is in the RUNNABLE
state, the thread scheduler may be executing on a CPU core. If there are more RUNNABLE
threads than cores, the scheduler will divide up the time each thread gets to actually execute. Setting the thread priority using Thread.setPriority(int newPriority)
can help the scheduler determine which thread should get more CPU time.
Thread Name
To make it easier to identify and distinguish threads, within a debugger for example, you may give a thread an interesting name using Thread.setName(String name)
.
Starting a New Thread
The main motive of creating a new Thread
instance is to potentially allow some activity to run simultaneously with other activity. Creating and starting a new thread is as simple as calling Thread.start()
.
But such a thread will not do anything. Rather than calling its default constructor, call the constructor that takes a java.lang.Runnable
interface. Runnable
uses the strategy design pattern to specify in its Runnable.run()
method the activity that should occur while running in a thread.
Immediately after calling Thread.start()
, the method will return and your program will continue executing on the next line! What has happened behind the scenes is that a thread has been scheduled to execute the code within the Runnable
—in fact it may already be running, even while the program that created that thread continues to execute!
Terminating a Thread
A thread will automatically terminate when its path of execution completes the Runnable.run()
method (either by reaching the end of the method, encountering return
, or throwing an exception). Once a thread has terminated, you cannot call Thread.start()
to run it again.
Many times you want a thread to continue running until it is told to stop. The correct way to implement this is to have a flag or other condition to indicate that the thread should end. The thread will check this condition and leave the Runnable.run()
method when the condition is recognized. For example, one could have a Runnable
implementation BlinkerController
that controls the blinker on a car. Once started, it would continue until it was told it should be turned using BlinkerController.turnOff()
.
Some program can start the blinker by running an instance of BlinkerController
in a Thread
object. When it come time to turn off the blinker, the other thread will call BlinkerController.turnOff()
. The thread controlling the blinker will recognize that the flag was set, and exit the run()
method.
Sleeping Threads
The above blinker controller will work, but it will blink
very quickly, immediately alternating between the blinker on
and blinker off
state. We expect blinkers to pause between each state—perhaps for half a second. The naive approach to adding this delay might be to check the system clock using System.currentTimetMilliseconds()
and loop until 500 milliseconds had passed:
The problem with this approach is that while the thread is waiting for 500 milliseconds to pass, it isn't really doing nothing
; it is in fact looping and checking the current time, over and over. This takes up CPU resources which other threads could be using. If a thread really wants to do nothing for some amount of time, it can sleep by calling Thread.sleep(long milliseconds)
. This indicates that the operating system should suspend the execution of the current thread for approximately the amount of time requested before the thread is woke up
to continue execution. (The exact amount of time may vary based on system load and precision of the system clock.)
Interrupting a Thread
If a thread is sleeping or waiting on some I/O to occur (such as reading from a traditional hard drive), another thread can interrupt it by calling the Thread.interrupt()
method of the Thread
instance. A thread must be programmed to handle interruption. Interrupting a thread sets its interrupt status flag.
- If the thread is waiting in a method such as
Thread.sleep(…)
, the thread will unset the interrupt status and throw anInterruptedException
. - If the thread is running normally, the next time it tries to enter a method such as
Thread.sleep(…)
the thread will unset the interrupt status and throw anInterruptedException
.
So if a thread uses a method that can throw InterruptedException
, the thread will usually handle the interrupt status automatically and need only deal with the generated InterruptedException
.
BlinkerController
class that will interrupt the sleeping thread when the blinker is requested to be turned off, so that the blinker thread may immediately wake up and check its turnOff
variable when turnOff()
is called. As a bonus, the updated class also allows the amount of delay to be customized using the java.util.concurrent.TimeUnit
enum to ease the representation of time values.
Now we would create and start the blinker controller thread like this:
Joining a Thread
Using the classes and techniques described in this lesson, you could wind up with two, three, or hundreds of threads running at the same time. Some of them could be controlling a blinker; others could be performing a calculation. You might want one thread to wait until another thread has completed before continuing. You can use the Thread.join()
method to join the threads of execution together, so that the calling thread essentially sleeps until the thread on which join()
is called completes.
Daemon Threads
By default a Java program will not end until all threads that have been started by the program have finished. If you don't want a running thread to hold up the completion of a program, you can call Thread.setDaemon(boolean on)
on a thread with the value of true
. This will make the thread a daemon and will not prevent the program from ending, even if the thread is still running.
Review
Gotchas
- If a variable will be accessed by more than one thread, don't forget to mark it as
volatile
, or the value read by one thread may not be up to date. - Calling
Thread.sleep(…)
will call the currently executing thread to sleep, regardless of which class calls theThread.sleep(…)
method.
In the Real World
- Managing threads is extremely complicated. The approaches discussed in this lesson only touch the beginnings of thread management. It is better to use higher-level libraries and frameworks, discussed in upcoming lessons, to manage threads and concurrent tasks.
Think About It
- Do not
eat
exceptions by ignoring them. AnInterruptedException
, like most exceptions, usually indicates an error condition. If you are reading from a disk and anInterruptedException
, for example, there was a read error, pure and simple, and you should propagate the exception or throw anIOException
. This rule is only relaxed if you have some distinct state management such as a loop flag, and you are using thread interruption as a signal tostop sleeping and check the loop flag
. In such circumstances you can continue after receiving theInterruptedException
but only if you in fact check the loop flag soon afterwards.
Self Evaluation
- How is parallelization distinct from general concurrency?
- What is the difference between a process and a thread?
Task
- Add a new
subscribe
command-line option to the Booker program to subscribe to a periodical, identified using the--issn
parameter, for one year's worth of issues. Subscribing to a periodical will cause the Booker program to print some string for each issue subscribed to the publication, describing that issue.- Printing the issues will be performed by a singleton
SubscriptionService
implementation, accessed from the mainBookerService
implementation. - Printing the issues will be performed concurrently to the main application code.
- There will be a delay between printing issues, with one second representing each subscription week between issues.
- The subscription service need not handle multiple subscriptions. The subscription service should detect of another subscription is in progress and throw an
InvalidStateException
if that is the case (although currently the Booker program has no way to allow this to happen).
- Printing the issues will be performed by a singleton
- Print out a message of success in your main thread after the subscription has started. If implemented correctly, this message will be printed before the subscription ends.
- Print out a message in your main thread after the subscription ends by joining the subscription thread. You may want to achieve this by having the
BookerService
return the subscription thread when a subscription begins; or by having the service(s) provide a method for waiting for the subscription to end, thereby hiding the thread reference and the actual joining inside the implementation.
booker list [--debug] [--locale <locale>] [--name <name>] [--type (book|periodical)]
booker load-snapshot [--debug] [--locale <locale>]
booker purchase --isbn <ISBN> [--debug] [--locale <locale>]
booker subscribe --issn <ISSN> [--debug] [--locale <locale>]
booker -h | --help
Option | Alias | Description |
---|---|---|
list | Lists all available publications. | |
load-snapshot | Loads the snapshot list of publications into the current repository. | |
purchase | Removes a single copy of the book identified by ISBN from stock. | |
subscribe | Subscribes to a year's worth of issues of the periodical identified by ISSN. | |
--debug | -d | Includes debug information in the logs. |
--help | -h | Prints out a help summary of available switches. |
--isbn | Identifies a book, for example for the purchase command. | |
--issn | Identifies a periodical, for example for the subscribe command. | |
--locale | -l | Indicates the locale to use in the program, overriding the system default. The value is in language tag format. |
--name | -n | Indicates a filter by name for the list command. |
--type | -t | Indicates the type of publication to list, either book or periodical. If not present, all publications will be listed. |
See Also
- Thread Objects (Oracle - The Java™ Tutorials)
- Java 8 Concurrency Tutorial Part 1: Threads and Executors (Benjamin Winterberg)
- Java theory and practice: Dealing with InterruptedException (IBM developerWorks)