A thread is a single flow of control within a program. It is sometimes called the execution context because each thread must have its own resources, like the program counter and the execution stack, as the context for execution. However, all threads in a program still share many resources, such as memory space and opened files. Therefore, a thread may also be called a lightweight process.
NOTE: It is easier to create and destroy a thread than a process.
Parallel and Concurrent Threads
When two threads run in parallel, they are both being executed at the same time on different CPUs. Two concurrent threads are both in progress, or trying to get some CPU time for execution at the same time, but are not necessarily being executed simultaneously on different CPUs.
Improved CPU Utilization
A program may spend a big portion of its execution time waiting. For
example, it may wait for a resource to become accessible in an I/O operation,
or it may wait for a time-out to occur to start drawing the next scene
of an animation sequence. To improve CPU utilization, tasks with potentially
long waits can run as separate threads. Once a task starts waiting for
something to happen, the Java run time can choose another runnable task
for execution
Single-Threaded Program Example
In the program shown here, a run() method in the NoThreadPseudoIO class
is created to simulate a 10-second I/O operation. The main program will
first perform the simulated I/O operation
Single-Threaded Program Methods
The method showElapsedTime() is defined to print the time elapsed in
seconds since the program started, together with a user-supplied message.
The currentTimeMillis() method of the System class in the java.lang package
will return a long integer for the time difference, measured in milliseconds,
between the current time and 00:00:00 GMT on January 1, 1970.
NOTE: The times shown will vary depending on machine types and environments.
Single-Threaded Program Methods
The method showElapsedTime() is defined to print the time elapsed in
seconds since the program started, together with a user-supplied message.
The currentTimeMillis() method of the System class in the java.lang package
will return a long integer for the time difference, measured in milliseconds,
between the current time and 00:00:00 GMT on January 1, 1970.
NOTE: The times shown will vary depending on machine types and environments.
Multithreaded Program Example
The multithreaded program declares the class for the simulated I/O
operation as a subclass of the Thread class. After the thread is created,
the multithreaded program uses the start() method of the Thread class to
start the I/O operation. The start() method in turn calls the run() method
of the subclass.
Creating and Running Threads
There are two methods for making a task run concurrently with other
tasks: create a new class as a subclass of the Thread class or declare
a class by implementing the Runnable interface
Use Subclass
When you create a subclass of the Thread class, this subclass should
define its own run() method to override the run() method of the Thread
class. This run() method is where the task is performed.
Execute run() Method
The run() method is the first user-defined method the Java runtime
calls when a thread is started. An instance of the subclass is then created
by a new statement, followed by a call to the thread's start() method to
have the run() method executed.
Implement Runnable Interface
The Runnable interface requires only one method to be implemented-the
run() method. You first create an instance of this class with a new statement,
followed by the creation of a Thread instance with another new statement.
Finally, call this thread instance's start() method to start performing
the task defined in the run() method
Indicate run() Method
A class instance with the run() method defined within it must be passed
in as an argument in creating the Thread instance, so that when the start()
method of this Thread instance is called, Java run time knows which run()
method to execute.
Re-Implement Multithreaded Program
A multithreaded program can be re-implemented using the Runnable interface
by first changing the class definition to implement the Runnable interface,
instead of subclassing the Thread class. An instance of the class is created
and passed to a newly created Thread instance, followed by a call to the
start() method to start the execution of the run() method.
Controlling Thread Executions
Many methods defined in the java.lang.Thread class control the running
of a thread. Java 2 deprecated several of them to prevent data inconsistencies
or deadlocks. If you are just starting with Java 2, avoid the deprecated
methods and use the equivalent behavior. However, if you are transitioning
from Java 1.0 or 1.1, you will need to modify your code to avoid the deprecated
methods if you used them. Some of the most commonly used methods are examined
in this section.
void start
The void start() method is used to start the execution of the thread
body defined in the run() method. Program control will be immediately returned
to the caller, and a new thread will be scheduled to execute the run()
method concurrently with the caller's thread.
void stop
The void stop() method is deprecated and used to stop the execution
of the thread no matter what the thread is doing. The thread is then considered
dead, the internal states of the thread are cleared, and the resources
allocated are reclaimed. Using this method has the potential to leave data
in an inconsistent state and should be avoided.
void suspend
The void suspend() method is deprecated and used to temporarily stop
the execution of the thread. All the states and resources of the thread
are retained. The thread can later be restarted by another thread calling
the resume() method. Using this method has a strong potential for deadlocks
and should be avoided. You should use the Object.wait() method instead.
void resume
The void resume() method is deprecated and used to resume the execution
of a suspended thread. The suspended thread will be scheduled to run. If
it has a higher priority than the running thread, the running thread will
be preempted; otherwise, the just-resumed thread will wait in the queue
for its turn to run. Using this method has a strong potential for deadlocks
and should be avoided. You should use the Object.notify() method instead.
static void sleep
The static void sleep() method is a class method that causes the Java
run time to put the caller thread to sleep for a minimum of the specified
time period. The exception, InterruptedException, may be thrown while a
thread is sleeping or any time if you interrupt() it. Either a try-catch
statement needs to be defined to handle this exception or the enclosing
method needs to have this exception in the throws clause.
void join
The void join() method is used for the caller's thread to wait for
this thread to die-for example, by coming to the end of the run() method.
static void yield
The static void yield() method is a class method that temporarily stops
the caller's thread and puts it at the end of the queue to wait for another
turn to be executed. It is used to make sure other threads of the same
priority have the chance to run.
Caller's Thread
All the class methods defined in the Thread class, such as sleep()
and yield(), will act on the caller's thread. That is, it is the caller's
thread that will sleep for a while or yield to others.
NOTE: The reason is that a class method can never access an instance's data or method members unless the instance is passed in as an argument, created inside the method, or stored in a class variable visible to the method.
Thread Execution Example
In the example shown here, the main thread creates two threads, and
then waits for the first thread to finish by calling the first thread's
join() method. The first thread calls the sleep() method to be asleep for
10 seconds.
Second Thread
The second thread calls its own wait() method to suspend itself. After
the first thread comes to an end, the main thread will resume its execution,
wake up the second thread by calling the second thread's notify() method,
and wait until the second thread also comes to an end by calling the second
thread's join() method.
Thread Life Cycle
Every thread, after creation and before destruction, will always be
in one of four states: newly created, runnable, blocked, or dead
New Threads
A thread enters the newly created state immediately after creation;
that is, it enters the state right after the thread-creating new statement
is executed. In this state, the local data members are allocated and initialized,
but execution of the run() method will not begin until its start() method
is called. After the start() method is called, the thread will be put into
the runnable state.
Runnable Threads
When a thread is in the runnable state, the execution context exists
and the thread can be scheduled to run at any time; that is, the thread
is not waiting for any event to happen.
Running and Queued States
The runnable state can be subdivided into two substates: the running
and queued states. When a thread is in the running state, it is assigned
CPU cycles and is actually running. When a thread is in the queued state,
it is waiting in the queue and competing for its turn to spend CPU cycles.
The transition between these two substates is controlled by the virtual
machine scheduler.
NOTE: A thread can call the yield() method to voluntarily move itself to the queued state from the running state.
Blocked Threads
The blocked state is entered when one of these events occurs: the thread
or another thread calls the suspend() method; the thread calls an object's
wait() method; the thread calls the sleep() method; the thread is waiting
for an I/O operation to complete; or the thread will join() with another
thread.
CPU Cycle Competition
A thread in a blocked state will not be scheduled for running. It will
go back to the runnable state, competing for CPU cycles, when the counter-event
for the blocking event occurs.
Dead Threads
The dead state is entered when a thread finishes its execution or is
stopped by another thread calling its stop() method.
NOTE: The stop method is deprecated.
Boolean Conditions
To avoid the use of stop(), the proper way to exit out of a while (true)
loop is to maintain a state variable that is used as the while loop condition
check. So, instead of using stop() to halt the thread, you would change
the test case to be a boolean condition. Then, when you want the thread
to stop, instead of calling stop(), you change the state of the boolean.
This causes the thread to stop on the next pass and ensures that the thread
does not leave data in an inconsistent state.
//----------------------
boolean done = false;
public void run() {
while(!done) {
....
}
}
public safeStop() {
done = true;
}
//-------------------------
Thread Status
To find out whether a thread is alive-that is, currently runnable or
blocked-use the thread's isAlive() method. It will return true if the thread
is alive.
NOTE: If a thread is alive, it does not mean it is running, just that it can run.
Thread Synchronization
Introduction
All threads in a program share the same memory space, making it possible
for two threads to access the same variable or run the same method of the
same object at the same time.
Potential Problems
Problems may occur when multiple threads are accessing the same data
concurrently. Threads may race each other, and one thread may overwrite
the data just written by another thread. Or, one thread may work on another
thread's intermediate result and break the consistency of the data. A mechanism
is needed to block one thread's access to the critical data, if the data
is being worked on by another thread.
Bank Account Example
Suppose you have a program to handle a user's bank account. There are
three subtasks in making a deposit for the user: get the current balance
from a remote server, which may take as long as five seconds; add the newly
deposited amount into the just-acquired balance; and send the new balance
back to the same remote server, which, again, may take as long as five
seconds to complete.
Deposit Scenario
If two depositing threads, each making a $1,000 deposit, are started
at roughly the same time on a current balance of $1,000, the final balance
of these two deposits may reflect the result of only one deposit. In the
possible scenario shown here, the balance stored in the remote server increases
by only one deposit amount.
Deposit Scenario Program
The program shown here simulates the deposit scenario. An Account class
is defined with three methods: getBalance() to fetch the current balance
from some pseudo-server, with a simulated five-second delay; setBalance()
to write back the new balance to the same pseudo-server, with (again) a
simulated five-second delay; and deposit() to use the other two methods
to complete a deposit transaction.
Start Deposit Operation
A DepositThread class is declared to start the deposit operation on
the account. The main program creates an account instance and then starts
two threads to make a deposit of $1,000 each to that account.
Monitor Model
Java uses the idea of monitors to synchronize access to data. A monitor
is a guarded place where all the protected resources have the same locks.
Only a single key fits all the locks inside a monitor, and a thread must
get the key to enter the monitor and access these protected resources.
Monitor Key
If many threads want to enter the monitor simultaneously, only one
thread is handed the key; the others must wait outside until the key-holding
thread finishes its use of the resources and hands the key back to the
Java virtual machine.
NOTE: Deadlock may occur if threads are waiting for each other's key to proceed.
Thread Access
Once a thread gets a monitor's key, the thread can access any of the
resources controlled by that monitor countless times, as long as the thread
still owns the key. However, if this key-holding thread wants to access
the resources controlled by another monitor, the thread must get that particular
monitor's key.
Multiple Monitor Keys
At any time, a thread can hold many monitors' keys. Different threads
can hold keys for different monitors at the same time.
Protected Resources
In Java, the resources protected by monitors are program fragments
in the form of methods or blocks of statements enclosed in curly braces.
If some data can be accessed only through methods or blocks protected by
the same monitor, access to the data is indirectly synchronized.
synchronized Keyword
You use the keyword synchronized to indicate that the following method
or block of statements is to be synchronized by a monitor. When a block
of statements is to be synchronized, an object instance enclosed in parentheses
immediately following the synchronized keyword is required so the Java
virtual machine knows which monitor to check. For example, the deposit()
method can be synchronized to allow only one thread to run at a time. The
only change needed is a synchronized keyword before the method definition.
Called Object Synchronization
Alternatively, a block of statements in the deposit() method can be
synchronized on the called object.
Alternative Program
The output of the alternative program is almost the same, except the
first message from the second thread will be interleaved in the messages
from the first thread, because the first println() method is not inside
the synchronized block.
Unique Key
One unique key will be issued to every object containing a synchronized
instance method or being referred by a synchronized block. For synchronized
class methods, the key is issued to the class because the method may be
called before any class instances exist. This means that every object and
every class can have a monitor if there are any synchronized methods or
blocks of statements associated with them. Furthermore, a class monitor's
key is different from any of the keys of its class instance monitors