Chapter-4 (MultiThreading)
Chapter-4 (MultiThreading)
By: Joseph W.
1
Outline
Introduction
Thread States and Life Cycle
Thread Priorities and Thread Scheduling
The Thread Class and the Runnable Interface
The Main Thread
Creating A Thread (using Runnable Interface and Thread Class)
Using isAlive( ) and join( )
Synchronization
Interthread Communication
Obtaining Thread State
Using Executor Framework
Multithreading in JavaFX
5/18/2022 2
Introduction
This chapter presents Java’s capabilities for creating and managing multiple tasks which can
greatly improve program performance and responsiveness.
Java provides built-in support for multithreaded programming.
A multithreaded program contains two or more parts that can run concurrently.
Each part of such a program is called a thread, and each thread defines a separate path of
execution.
Thus, multithreading is a specialized form of multitasking.
Multitasking is supported by virtually all modern operating systems. However, there are two distinct
types of multitasking:
o Process-based and
o Thread-based
5/18/2022 3
Cont.
Process-Oriented
A process is, in essence, a program that is executing.
Thus, process-based multitasking is the feature that allows your computer to run two or more
programs concurrently. Example: Listening music and composing a document on a text editor.
In process-based multitasking, a program is the smallest unit of code that can be dispatched by
the scheduler.
Thread-Oriented
In a thread-based multitasking environment, the thread is the smallest unit of dispatchable code.
This means that a single program can perform two or more tasks simultaneously. Example: a text
editor can format text at the same time that it is printing, as long as these two actions are being
performed by two separate threads.
5/18/2022 4
Cont.
Processes are heavyweight tasks that require their own separate address spaces.
Inter-process communication is expensive and limited. Context switching from one process to
another is also costly.
Threads, on the other hand, are lighter weight.
While Java programs make use of process-based multitasking environments, process-based
multitasking is not under Java’s direct control. However, multithreaded multitasking is.
Java makes concurrency available to you through the language and APIs.
Java programs can have multiple threads of execution, each with its own method-call stack and
program counter, allowing it to execute concurrently with other threads while sharing with them
applicationwide resources such as memory and file handles.
Writing multithreaded programs can be tricky. Although the human mind can perform functions
concurrently, people find it difficult to jump between parallel trains of thought.
5/18/2022 5
Thread States and Life Cycle
At any time, a thread is said to be in one of several thread states—illustrated in the UML state
diagram in below.
Java hides most of this detail from you, greatly simplifying the task of developing multithreaded
applications.
5/18/2022 6
Cont.
New and Runnable States
A new thread begins its life cycle in the new state. It remains in this state until the program starts the
thread, which places it in the runnable state.
A thread in the runnable state is considered to be executing its task.
Waiting State
Sometimes a runnable thread transitions to the waiting state while it waits for another thread to
perform a task.
A waiting thread transitions back to the runnable state only when another thread notifies it to continue
executing.
Timed Waiting State
A runnable thread can enter the timed waiting state for a specified interval of time. It transitions back
to the runnable state when that time interval expires or when the event it’s waiting for occurs.
5/18/2022 7
Cont.
Timed waiting threads and waiting threads cannot use a processor, even if one is available.
Another way to place a thread in the timed waiting state is to put a runnable thread to sleep—a
sleeping thread remains in the timed waiting state for a designated period of time (called a sleep
interval), after which it returns to the runnable state.
For example, a word processor may contain a thread that periodically backs up (i.e., writes a copy
of) the current document to disk for recovery purposes where a thread sleeps between successive
backups.
Blocked State
A runnable thread transitions to the blocked state when it attempts to perform a task that cannot be
completed immediately and it must temporarily wait until that task completes.
For example, when a thread issues an input/output request, the operating system blocks the thread
from executing until that I/O request completes—at that point, the blocked thread transitions to the
runnable state, so it can resume execution.
5/18/2022 8
Cont.
Terminated State
A runnable thread enters the terminated state (sometimes called the dead state) when it
successfully completes its task or otherwise terminates (perhaps due to an error).
5/18/2022 9
Thread Priorities and Thread Scheduling
Every Java thread has a thread priority that helps determine the order in which threads are
scheduled.
Each new thread inherits the priority of the thread that created it (i.e. spawned it).
Thread priorities cannot guarantee the order in which threads execute.
Most operating systems support timeslicing, which enables threads of equal priority to share a
processor.
An operating system’s thread scheduler determines which thread runs next.
One simple thread-scheduler implementation keeps the highest-priority thread running at all times
and, if there’s more than one highest-priority thread, ensures that all such threads execute for a
quantum each in round-robin fashion.
When a higher-priority thread enters the ready state, the operating system generally preempts
the running thread.
5/18/2022 10
Cont.
Depending on the operating system, a steady influx of higher-priority threads could postpone
possibly indefinitely, the execution of lower-priority threads.
Such indefinite postponement is sometimes referred to more colourfully as starvation.
Operating systems employ a technique called aging to prevent starvation—as a thread waits in the
ready state, the operating system gradually increases the thread’s priority to ensure that the
thread will eventually run.
Another problem related to indefinite postponement is called deadlock. This occurs when a waiting
thread (let’s call this thread1) cannot proceed because it’s waiting (either directly or indirectly) for
another thread (let’s call this thread2) to proceed, while simultaneously thread2 cannot proceed
because it’s waiting (either directly or indirectly) for thread1 to proceed.
5/18/2022 11
The Thread Class and the Runnable Interface
Java’s multithreading system is built upon the Thread class, its methods, and its companion
interface, Runnable.
Thread encapsulates a thread of execution.
Since you can’t directly refer to the ethereal state of a running thread, you will deal with it through its
proxy, the Thread instance that spawned it.
To create a new thread, your program will either extend Thread or implement the Runnable
interface.
The Thread class defines several methods that help manage threads. Several of those used in this
chapter are shown in the table below:
5/18/2022 12
Cont.
5/18/2022 13
The Main Thread
Main thread is the one that is executed when your program begins.
The main thread is important for two reasons:
o It is the thread from which other “child” threads will be spawned.
o Often, it must be the last thread to finish execution because it performs various shutdown
actions.
Although the main thread is created automatically when your program is started, it can be controlled
through a Thread object. To do so, you must obtain a reference to it by calling the method
currentThread( ), which is a public static member of Thread. Its general form is shown here:
static Thread currentThread( )
Example:
5/18/2022 14
Cont.
5/18/2022 15
Cont.
In this program, a reference to the current thread (the main thread, in this case) is obtained by
calling currentThread( ), and this reference is stored in the local variable t.
The program then calls setName( ) to change the internal name of the thread.
Next, a loop counts down from five, pausing one second between each line. The pause is
accomplished by the sleep( ) method.
The argument to sleep( ) specifies the delay period in milliseconds.
Notice the try/catch block around this loop. The sleep( ) method in Thread might throw an
InterruptedException.
Output of the above program is
5/18/2022 16
Cont.
Notice the output produced when t is used as an argument to println( ). This displays, in order: the
name of the thread, its priority, and the name of its group.
A thread group is a data structure that controls the state of a collection of threads as a whole.
The sleep( ) method causes the thread from which it is called to suspend execution for the specified
period of milliseconds. Its general form is shown here:
static void sleep(long milliseconds) throws InterruptedException
As the preceding program shows, you can set the name of a thread by using setName( ). You can
obtain the name of a thread by calling getName( ). These methods are members of the Thread
class and are declared like this:
final void setName(String threadName)
final String getName( )
Here, threadName specifies the name of the thread.
5/18/2022 17
Cont.
In the most general sense, you create a thread by instantiating an object of type Thread.
Java defines two ways in which this can be accomplished:
o You can implement the Runnable interface.
o You can extend the Thread class, itself.
5/18/2022 18
Creating A Thread (Implementing Runnable)
The easiest way to create a thread is to create a class that implements the Runnable interface.
You can construct a thread on any object that implements Runnable.
To implement Runnable, a class need only implement a single method called run( ), which is
declared like this:
public void run( )
Inside run( ), you will define the code that constitutes the new thread.
It is important to understand that run( ) can call other methods, use other classes, and declare
variables, just like the main thread can.
This thread will end when run( ) returns.
After you create a class that implements Runnable, you will instantiate an object of type Thread
from within that class.
5/18/2022 19
Cont.
Thread defines several constructors. One of them is:
Thread(Runnable threadOb, String threadName)
In this constructor, threadOb is an instance of a class that implements the Runnable interface.
The name of the new thread is specified by threadName.
After the new thread is created, it will not start running until you call its start( ) method, which is
declared within Thread. In essence, start( ) initiates a call to run( ).
The start( ) method is shown here:
void start( )
Example:
5/18/2022 20
Cont.
5/18/2022 21
Cont.
5/18/2022 22
Cont.
The output of the above program is :
5/18/2022 23
Creating A Thread (Extending Thread)
The second way to create a thread is to create a new class that extends Thread, and then to create
an instance of that class.
The extending class must override the run( ) method, which is the entry point for the new thread.
As before, a call to start( ) begins execution of the new thread.
Next is the preceding program rewritten to extend Thread:
5/18/2022 24
Cont.
5/18/2022 25
Cont.
5/18/2022 26
Using isAlive( ) and join( )
Often you will want the main thread to finish last. In the preceding examples, this is accomplished
by calling sleep( ) within main( ), with a long enough delay to ensure that all child threads terminate
prior to the main thread.
However, this is hardly a satisfactory solution, and it also raises a larger question: How can one
thread know when another thread has ended?
Two ways exist to determine whether a thread has finished.
o Call isAlive( ) on the thread. This method is defined by Thread, and its general form is shown
here:
final boolean isAlive( )
✓ The isAlive( ) method returns true if the thread upon which it is called is still running. It
returns false otherwise.
5/18/2022 27
Cont.
o join() method: while isAlive( ) is occasionally useful, the method that you will more commonly
use to wait for a thread to finish is called join( ), shown here:
final void join( ) throws InterruptedException
✓ This method waits until the thread on which it is called terminates.
✓ Its name comes from the concept of the calling thread waiting until the specified thread
joins it.
Here is an improved version of the preceding example that uses join( ) to ensure that the main
thread is the last to stop. It also demonstrates the isAlive() method.
.
5/18/2022 28
Cont.
5/18/2022 29
Cont.
5/18/2022 30
Cont.
5/18/2022 31
Cont.
5/18/2022 32
Cont.
The output of the above program looks like:
5/18/2022 33
Synchronization
When two or more threads need access to a shared resource, they need some way to ensure that
the resource will be used by only one thread at a time. The process by which this is achieved is
called synchronization.
Key to synchronization is the concept of the monitor.
A monitor is an object that is used as a mutually exclusive lock.
Only one thread can own a monitor at a given time. When a thread acquires a lock, it is said to
have entered the monitor.
All other threads attempting to enter the locked monitor will be suspended until the first thread exits
the monitor. These other threads are said to be waiting for the monitor.
Synchronization is easy in Java, because all objects have their own implicit monitor associated
with them.
To enter an object’s monitor, just call a method that has been modified with the synchronized
keyword.
5/18/2022 34
Example Program without Synchronization
5/18/2022 35
Cont.
5/18/2022 36
Cont.
5/18/2022 37
Cont.
Output from the previous program:
As you can see, by calling sleep( ), the call( ) method allows execution to switch to another thread.
This results in the mixed-up output of the three message strings.
This is known as a race condition, because the three threads are racing each other to complete
the method.
To fix the preceding program, you must serialize access to call( ). To do this, you simply need to
precede call( )’s definition with the keyword synchronized, as shown in next slide:
5/18/2022 38
Cont.
Imagine that you want to synchronize access to objects of a class that was not designed for
multithreaded access. That is, the class does not use synchronized methods. How can access to
an object of this class be synchronized?
When the methods of a class doesn’t use synchronized keyword: You simply put calls to the
methods defined by this class inside a synchronized block.
This is the general form of the synchronized statement:
Synchronized(objRef) {
// Statements to be synchronized
}
Here, objRef is a reference to the object being synchronized.
A synchronized block ensures that a call to a synchronized method that is a member of objRef’s
class occurs only after the current thread has successfully entered objRef’s monitor.
5/18/2022 39
Interthread Communication
Java includes an elegant interprocess communication mechanism via the wait( ), notify( ), and
notifyAll( ) methods.
These methods are implemented as final methods in Object, so all classes have them. All three
methods can be called only from within a synchronized context.
These methods are declared within Object, as shown here:
final void wait( ) throws InterruptedException
final void notify( )
final void notify All( )
Although wait( ) normally waits until notify( ) or notifyAll( ) is called, there is a possibility that in
very rare cases the waiting thread could be awakened due to a spurious wakeup.
Because of this remote possibility, the Java API documentation recommends that calls to wait( )
should take place within a loop that checks the condition on which the thread is waiting.
Example: Producer-Consumer
5/18/2022 40
Cont.
5/18/2022 41
Cont.
5/18/2022 42
Cont.
5/18/2022 43
Cont.
5/18/2022 44
Cont.
5/18/2022 45
Obtaining a Thread’s State
As mentioned earlier in this chapter, a thread can exist in a number of different states.
You can obtain the current state of a thread by calling the getState( ) method defined by Thread.
It is shown here:
Thread.State getState( )
It returns a value of type Thread.State that indicates the state of the thread at the time at which the
call was made.
State is an enumeration defined by Thread. (An enumeration is a list of named constants)
5/18/2022 46
Creating and Executing Threads with Executor Framework
The Executor interface declares a single method named execute which accepts a Runnable as
an argument.
The Executor assigns every Runnable passed to its execute method to one of the available threads
in the thread pool.
If there are no available threads, the Executor creates a new thread or waits for a thread to become
available and assigns that thread the Runnable that was passed to method execute.
When an Executor begins executing a Runnable, the Executor calls the Runnable object’s run
method.
Using an Executor has many advantages over creating threads yourself. Executors can reuse
existing threads to eliminate the overhead of creating a new thread for each task and can
improve performance by optimizing the number of threads to ensure that the processor
stays busy, without creating so many threads that the application runs out of resources.
5/18/2022 47
Cont.
The ExecutorService interface (of package java.util.concurrent) extends Executor and declares
various methods for managing the life cycle of an Executor.
You obtain an ExecutorService object by calling one of the static methods declared in class
Executors.
Example: below is an example that uses Executor Framework
5/18/2022 48
Cont.
5/18/2022 49
Cont.
5/18/2022 50
Cont.
5/18/2022 51
Cont.
5/18/2022 52
Cont.
Lines 9-11 in the code above create and name three Tasks to execute.
Line 13 uses Executors method newCachedThreadPool to obtain an ExecutorService that creates
new threads if no existing threads are available to reuse.
Line 19 calls ExecutorService method shutdown, which prevents the ExecutorService from
accepting new tasks, but continues executing tasks that have already been submitted.
Once all of the previously submitted tasks have completed, the ExecutorService terminates.
Interface ExecutorService provides the awaitTermination method that returns control to its caller
either when all tasks executing in the ExecutorService complete or when the specified timeout
elapses. If all tasks are completed before awaitTermination times out, this method returns true;
otherwise it returns false.
The two arguments to awaitTermination represent a timeout value and a unit of measure specified
with a constant from class TimeUnit (in this case, TimeUnit.MINUTES).
5/18/2022 53
Cont.
Method awaitTermination throws an InterruptedException if the calling thread is interrupted while
waiting for other threads to terminate.
Note: Interruption will happen to any thread that is in sleeping or waiting state (i.e. sleep() or wait()
is invoked). We can use thread’s interrupt() method, to manually interrupt the thread execution by
throwing InterruptedException.
Note: If the thread is not in the sleeping or waiting state then calling the interrupt() method performs
a normal behavior and doesn't interrupt the thread but sets the interrupt flag to true.
ExecutorServices’s shutdownNow method: attempts to stop all actively executing tasks, halts the
processing of waiting tasks, and returns a list of the tasks that were awaiting execution. This method
does not wait for actively executing tasks to terminate.
5/18/2022 54
Multithreading in JavaFX
All JavaFX applications have a single thread, called the JavaFX application thread, to handle
interactions with the application’s controls.
Typical interactions include rendering controls or processing user actions such as mouse clicks.
All tasks that require interaction with an application’s GUI are placed in an event queue and are
executed sequentially by the JavaFX application thread.
JavaFX’s scene graph is not thread safe—its nodes cannot be manipulated by multiple threads
without the risk of incorrect results that might corrupt the scene graph.
Unlike the other examples presented in this chapter, thread safety in JavaFX applications is
achieved not by synchronizing thread actions, but by ensuring that programs manipulate the
scene graph from only the JavaFX application thread. This technique is called thread confinement.
If an application must perform a lengthy task in response to a user interaction, the JavaFX
application thread cannot render controls or respond to events while the thread is tied up in that
task. This causes the GUI to become unresponsive.
5/18/2022 55
Cont.
It’s preferable to handle long-running tasks in separate threads, freeing the JavaFX application
thread to continue managing other GUI interactions.
JavaFX provides multiple mechanisms for updating the GUI from other threads.
o One is to call the static method runLater of class Platform (package javafx.application)
✓ This method receives a Runnable and schedules it on the JavaFX application thread for
execution at some point in the future. Such Runnables should perform only small updates,
so the GUI remains responsive.
o Interface Worker and classes Task and ScheduledService (package javafx.concurrent)
✓ Long-running or compute-intensive tasks should be performed on separate worker
threads, not the JavaFX application thread.
✓ A Worker is a task that should execute using one or more separate threads.
✓ Class Task is a Worker implementation that enables you to perform a task.
5/18/2022 56
Cont.
✓ Task implements several interfaces, including Runnable.
✓ Class Task also provides methods that are guaranteed to update its properties in the
JavaFX application thread, this enables programs to bind a Task’s properties to GUI
controls for automatic updating.
✓ Class ScheduledService is a Worker implementation that creates and manages a Task.
Unlike a Task, a ScheduledService can be reset and restarted.
Example:
5/18/2022 57
Cont.
5/18/2022 58
Cont.
5/18/2022 59
Cont.
5/18/2022 60
Cont.
5/18/2022 61
Cont.
5/18/2022 62
Cont.
5/18/2022 63
Cont.
5/18/2022 64
Thank You!!
65