Conceptually, a thread is a flow of control within a program. A thread is similar to the more familiar notion of a process, except that threads within the same application are much more closely related and share much of the same state. It’s kind of like a golf course, which many golfers use at the same time. The threads cooperate to share a working area. They have access to the same objects, including static and instance variables, within their application. However, threads have their own copies of local variables, just as players share the golf course but do not share some personal items like clubs and balls.
In single-processor systems, as shown in (b), the multiple threads share CPU time, known as time sharing, and the operating system is responsible for scheduling and allocating resources to them. This arrangement is practical, because most of the time the CPU is idle. It does nothing, for example, while waiting for the user to enter data.
Multithreading can make your program more responsive and interactive, as well as enhance performance. For example, a good word processor lets you print or save a file while you are typing. In some cases, multithreaded programs run faster than single-threaded programs even on single-processor systems. Java provides exceptionally good support for creating and running threads and for locking resources to prevent conflicts.
Multiple threads in an application have the same problems as the golfers—in a word, synchronization. Just as you can’t have two sets of players blindly playing the same green at the same time, you can’t have several threads trying to access the same variables without some kind of coordination. Someone is bound to get hurt. A thread can reserve the right to use an object until it’s finished with its task, just as a golf party gets exclusive rights to the green until it’s done. And a thread that is more important can raise its priority, asserting its right to play through.
In a running Java program, all code is executed in threads and within a thread everything happens sequentially, one instruction after another. When JVM launches, it creates one thread for the main
method to be executed in. From there, new threads can be created to execute code in parallel to the main one. The most basic way to do that is to use the Thread
class.
Java offers a Thread
class that can be used to launch new threads, wait for them to finish, or interact with them in more advanced ways.
Threads can be created two ways, either by extending java.lang.Thread
or by implementing java.lang.Runnable
.
Extending the Thread
class and overriding the run()
method can create a threadable class. This is an easy way to start a thread:
class Player extends Thread { public void run() { System.out.println("Strike"); } } Player miki = new Player(); miki.run();
Another way to create a thread, you need to define a block of code by implementing the Runnable
interface, which only has one abstract method run
. An instance of this implementation can then be passed to the Thread
‘s constructor.
Let’s start with an example that simulate three strike and waits for half a second after each strike.
class Player implements Runnable { private final int id; public Player(int id) { this.id = id; } @Override // This function will be executed in parallel public void run() { try { // Strike a ball three times for (int i = 0; i < 3; i++) { System.out.println("Strike " + i + " from Thread " + id); // Wait for half a second (500ms) Thread.sleep(500); } } catch (InterruptedException ex) { System.out.println("Thread was interrupted"); } } }
The InterruptedException
is thrown by Thread.sleep()
.
We can use the Runnable
implementation to create a Thread
instance.
Thread thread = new Thread(new Player(1));
With the thread in hand, it is time to launch it:
System.out.println("main() started"); Thread thread = new Thread(new Player(1)); thread.start(); System.out.println("main() finished"); main() started Strike 0 from Thread 1 main() finished Strike 1 from Thread 1 Strike 2 from Thread 1
Notice that strikes from the main
method and the thread we started are interleaving. This is because they run in parallel and their executions interleave unpredictably. In fact chances are, you will see slightly different output each time you run the program. At the same time, instructions within a single thread are always executed in the expected order as you can see from the increasing strike numbers.
So when does a thread finish? It happens in one of two cases:
Runnable
are executedrun
methodOne common task that we need to do with a thread is to wait until it is finished. In Java, this is pretty straightforward. All we need to do is to call the join
method on a thread instance:
System.out.println("main() started"); Thread thread = new Thread(new Player(1)); thread.start(); System.out.println("main() is waiting"); thread.join(); System.out.println("main() finished");
In this case, the calling thread will be blocked until the target thread is finished. When the last instruction in the target thread is executed the calling thread is resumed:
main() started main() is waiting Strike 0 from Thread 1 Strike 1 from Thread 1 Strike 2 from Thread 1 main() finished
Notice that "main() finished" is printed after all messages from the Player
. Using the join()
method this way we can ensure that some operations are executed strictly after all instructions in a particular thread. If we call join
on a thread that has already finished the call returns immediately and the calling thread is not being paused. This makes it easy to wait for several threads, just by looping over a collection of them and calling join
on each.
Thread States
Tasks are executed in threads. Threads can be in one of five states: New, Ready, Running, Blocked, or Finished.
So, a thread is always in one of the following six states (Lifecycle of a Thread):
Enumeration Thread.state
provides six thread states, as described below
NEW
- a thread that is created but not startedRUNNABLE
- a thread that is available to runBLOCKED
- an "alive" thread that is blocked waiting for a monitor lockWAITING
- an "alive" thread that calls its own wait()
or join()
while waiting on another threadTIMED_WAITING
- an "alive" thread that is waiting on another thread for a specified period of time; sleepingTERMINATED
- a thread that has completedThread Priorities
The valid range of priority values is typically 1 through 10, with a default value of 5. Thread priorities are one of the least portable aspects of Java, as their range and default values can vary among Java Virtual Machines (JVMs). Using MIN_PRIORITY
, NORM_PRIORITY
, and MAX_PRIORITY
can retrieve priorities.
System.out.print(Thread.MAX_PRIORITY);
Lower priority threads yield to higher priority threads.
Common Methods
Following is common methods used for threads from the Thread
class.
getPriority()
returns the thread’s prioritygetState()
returns the thread’s stateinterrupt()
interrupts the threadisAlive()
returns the thread’s alive statusisInterrupted()
checks for interruption of the threadjoin()
causes the thread that invokes this method to wait for the thread that this object represents to finishsetPriority(int)
sets the thread’s prioritystart()
places the thread into a runnable statesleep(long)
puts a thread to sleep for a specified time in millisecondsyield()
causes a thread to pause temporarily and allow other threads to execute.Using the join()
method in a program is useful if one of the threads cannot proceed until another thread has finished executing.
Following is common methods used for threads from the Object
class.
notify()
tells a thread to wake up and runnotifyAll()
tells all threads that are waiting on a thread or resource to wake up, and then the scheduler will select one of the threads to runwait()
pauses a thread in a wait state until another thread calls notify()
or notifyAll()
You must consider the following two rules before you call the wait()
method of an object.
Rule #1. The call to the wait()
method must be placed inside a synchronized method (static or non-static) or a synchronized block.
Rule #2. The wait()
method must be called on the object whose monitor the current thread has acquired. It throws a java.lang.InterruptedException
. The code that calls this method must handle this exception. The wait()
method throws an IllegalMonitorStateException
when the current thread is not the owner of the object’s monitor.
Synchronization
It’s possible for data to effectively become corrupted when it’s modified by more than one thread simultaneously. However, Java’s synchronized
keyword provides an easy way for you to prevent this from happening by allowing you to define methods and blocks of code that can be executed by only one thread at a time. In effect, the synchronized
keyword locks the method or block of code while it’s being executed by one thread so that no other threads are allowed to enter until the first thread has exited the method or block.
Each instance of java.lang.Object
or one of its subclasses (in other words, every Java object) maintains a lock (or monitor or mutex (mutually exclusive lock)), and the synchronized
keyword is always implicitly or explicitly associated with an instance of Object (primitives can’t be used). Before a thread can enter a synchronized method or section of code, it must obtain the monitor of the object associated with that code. If one thread obtains an object’s monitor and a second thread attempts to do so, the second thread becomes blocked and its execution is suspended until the monitor becomes available. In addition to the monitor, each object maintains a list of threads that are blocked because they’re waiting on the object’s monitor. If a thread can’t obtain an object’s monitor, it’s automatically put on the list, and once the monitor becomes available, one of the threads in the list will be given the monitor and allowed to continue execution. This behavior occurs when you use the synchronized
keyword, and you don’t need to explicitly obtain or release an object’s monitor. Instead, it will be automatically obtained (if possible) when a thread enters a synchronized method or block of code and released when the thread exits that code block or method.
The synchronized
keyword provides a means to apply locks to blocks and methods. Locks should be applied to blocks and methods that access critically shared resources. These monitor locks begin and end with opening and closing braces. Following are some examples of synchronized blocks and methods.
Object instance t
with a synchronized lock:
synchronized (t) { // block body }
Object instance this with a synchronized lock:
synchronized (this) { // block body }
Method raise()
with a synchronized lock:
// Equivalent code segment 1 synchronized void raise() { // method body } // Equivalent code segment 2 void raise() { synchronized (this) { // method body } }
Static method calibrate()
with a synchronized lock:
class Telescope { synchronized static void calibrate() { // method body } }
Volatile variables
I discussed the use of the synchronized
keyword in previous sections. Two things happen when a thread executes a synchronized method/block.
What can you do to achieve only the second point without using a synchronized
method/block? That is, how can you keep the values of variables in a thread’s working memory in sync with their values in the main memory? The answer is the keyword volatile
. You can declare a variable volatile
like so:
volatile boolean flag = true;
For every read request for a volatile
variable, a thread reads the value from the main memory. For every write request for a volatile
variable, a thread writes the value to the main memory. In other words, a thread does not cache the value of a volatile
variable in its working memory. Note that using a volatile
variable is useful only in a multi-threaded environment for variables that are shared among threads. It is faster and cheaper than using a synchronized
block.
You can declare only a class member variable (instance or static fields) as volatile
. You cannot declare a local variable as volatile
because a local variable is always private to the thread, which is never shared with other threads. You cannot declare a volatile
variable final
because the volatile
keyword is used with a variable that changes.
You can use a volatile
variable to stop a thread by using the variable’s value as a flag. If the flag is set, the thread can keep running. If another thread clears the flag, the thread should stop. Since two threads share the flag, you need to declare it volatile
, so that on every read the thread will get its updated value from the main memory.
Following listing demonstrates the use of a volatile
variable. If the keepRunning
variable is not declared volatile
, the JVM is free to run the while loop in the run()
method forever, as the initial value of keepRunning
is set to t
rue and a thread can cache this value in its working memory. Since the keepRunning
variable is declared volatile
, the JVM will read its value from the main memory every time it is used. When another thread updates the keepRunning
variable’s value to false using the stopThread()
method, the next iteration of the while
loop will read its updated value and stop the loop.
public class VolatileVariable extends Thread { private volatile boolean keepRunning = true; @Override public void run() { System.out.println("Thread started..."); // keepRunning is volatile. So, for every read, the thread reads its // latest value from the main memory while (keepRunning) { try { System.out.println("Going to sleep ..."); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Thread stopped..."); } public void stopThread() { this.keepRunning = false; } public static void main(String[] args) { // Create the thread VolatileVariable vv = new VolatileVariable(); // Start the thread vv.start(); // Let the main thread sleep for 3 seconds try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // Stop the thread System.out.println("Going to set the stop flag to true..."); vv.stopThread(); } }
Thread started... Going to sleep ... Going to sleep ... Going to sleep ... Going to set the stop flag to true... Thread stopped...
Concurrent Utilities
Java 2 SE 5.0 introduced utility classes for concurrent programming. These utilities reside in the java.util.concurrent
package, and they include executors, concurrent collections, synchronizers, and timing utilities.
The java.util.concurrent
package and its two subpackages, java.util.concurrent.atomic
and java.util.concurrent.locks
, include very useful concurrency constructs. You use them only when you are developing an advanced level multi-threaded program. We can categorize these concurrency features into four categories:
Executors
Above you have learned how to define a task class by implementing java.lang.Runnable
, and how to create a thread to run a task like this:
Runnable task = new TaskClass(task); new Thread(task).start();
This approach is convenient for a single task execution, but it is not efficient for a large number of tasks, because you have to create a thread for each task. Starting a new thread for each task could limit throughput and cause poor performance. Using a thread pool is an ideal way to manage the number of tasks executing concurrently. Java provides the Executor
interface for executing tasks in a thread pool and the ExecutorService
interface for managing and controlling tasks. ExecutorService
is a subinterface of Executor
.
The class Executors
provides factory (object creator) methods and utility methods. Of them, the following are supplied to create thread pools:
newCachedThreadPool()
creates an unbounded thread pool that automatically reuses threadsnewFixedThreadPool(int nThreads)
creates a fixed-size thread pool that automatically reuses threads off a shared unbounded queuenewScheduledThreadPool(int corePoolSize)
creates a thread pool that can have commands scheduled to run periodically or on a specified delaynewSingleThreadExecutor()
creates a single-threaded executor that operates off an unbounded queuenewSingleThreadScheduledExecutor()
creates a single-threaded executor that can have commands scheduled to run periodically or by a specified delayThe following example demonstrates usage of the newFixedThreadPool
factory method:
public class ThreadPoolExample { public static void main() { // Create tasks (class RTask implements Runnable') RTask t1 = new RTask("thread1"); RTask t2 = new RTask("thread2"); // Create thread manager ExecutorService threadExecutor = Executors.newFixedThreadPool(2); // Make threads runnable threadExecutor.execute(t1); threadExecutor.execute(t2); // Shut down threads threadExecutor.shutdown(); } }
The Fork/Join Framework
The fork/join framework is an implementation of the executor service whose focus is to solve those problems efficiently, which may use the divide-and-conquer algorithm by taking advantage of the multiple processors or multiple cores on a machine. The framework helps solve the problems that involve parallelism. Typically, the fork/join framework is suitable in a situation where
The fork/join framework creates a pool of threads to execute the subtasks. When a thread is waiting on a subtask to finish, the framework uses that thread to execute other pending subtasks of other threads. The technique of an idle thread executing other thread’s task is called work-stealing. The framework uses the work-stealing algorithm to enhance the performance. The following four classes in the java.util.concurrent
package are central to learning the fork/join framework:
Concurrent Collections
Even though collection types can be synchronized, it is best to use concurrent thread-safe classes that perform equivalent functionality, as represented below.
Collection class | Thread-safe equivalent |
---|---|
HashMap | ConcurrentHashMap |
TreeMap | ConcurrentSkipListMap |
TreeSet | ConcurrentSkipListSet |
Map subtypes | ConcurrentMap |
List subtypes | CopyOnWriteArrayList |
Set subtypes | CopyOnWriteArraySet |
PriorityQueue | PriorityBlockingQueue |
Deque | BlockingDeque |
Queue | BlockingQueue |
Synchronizers
Synchronizers are special-purpose synchronization tools. Available synchronizers are listed in following table.
Synchronizer | Description |
---|---|
Semaphore | Maintains a set of permits |
CountDownLatch | Implements waits against sets of operations being performed |
CyclicBarrer | Implements waits against common barrier points |
Exchanger | Implements a synchronization point where threads can exchange elements |
Daemon Threads
Each thread is classified as either a daemon thread or a user thread, and Thread
's setDaemon()
method allows you to specify the thread’s type. To use setDaemon()
, you must call it before a thread is started, and passing a boolean value of true
indicates that the thread should be a daemon thread, while false
(the default) indicates it should be a user thread.
The only difference between a daemon thread and a user thread is that one type (user) prevents the Java Virtual Machine from exiting, while the other (daemon) doesn’t.
Tasks with results: Callable and Future
Because the Runnable
interface was created for Thread
to consume, its API doesn’t allow for direct feedback to the caller. The new Callable
interface, which is effectively a replacement for Runnable
, rectifies this situation by providing a call()
method that both returns a result and can throw exceptions. Callable
is a generic class that is parameterized by the type it returns. The following examples create a Callable
that returns an integer:
class MyCallable implements Callable<Integer> { public Integer call() { return 2+2; } } // or anonymously Callable<Integer> callable = new Callable<Integer>() { public Integer call() { return 2+2; } };
There is also a convenience method for bridging Runnable
to Callable
in the Executor
class. It takes a Runnable
and a fixed value to return as a value when it completes:
Callable<Integer> callable = Executors.callable( runnable, 42 /*return value*/ );
The new Future
class is used with Callable
and serves as a handle to wait for and retrieve the result of the task or cancel the task before it is executed. A Future
is returned by the submit()
methods of an ExecutorService
, which is essentially a beefed-up Executor
.
Future<Integer> result = executorService.submit(callable); int val = result.get(); // blocks until ready
Future
is also a generic interface, which is parameterized by its return type. This explains the somewhat cute name. For example, a Future<Integer>
could be read as "a future integer". Future has both blocking and timed-wait get()
methods to retrieve the result when it is ready, as well as an isDone()
test method and a cancel()
method to stop the task if it hasn’t started yet. If the task has been cancelled, you get a CancellationException
if you attempt to retrieve the result.
Disadvantages of Using Threads
Outcome
Nowadays, Thread
is considered to be a low-level tool in multithreaded programming. Instead of explicitly creating threads you might want to use thread pools that limit the number of threads in your application and executors that allow executing asynchronous tasks.