JAVA 11 MultithreadedProgramming
JAVA 11 MultithreadedProgramming
Thread-Based Multi-Tasking
• Thread-based multi-tasking is about a single program
executing concurrently several tasks e.g. a text editor
printing and spell-checking text.
• Threads are lightweight tasks:
1) they share the same address space
2) they cooperatively share the same process
3) inter-thread communication is inexpensive
4) context-switching from one thread to another is
low-cost
• Java multi-tasking is thread-based.
Reasons for Multi-Threading
• Multi-threading enables to write efficient programs that
make the maximum use of the CPU, keeping the idle time to
a minimum.
• There is plenty of idle time for interactive, networked
applications:
1) the transmission rate of data over a network is much
slower than the rate at which the computer can process it
2) local file system resources can be read and written at a
much slower rate than can be processed by the CPU
3) of course, user input is much slower than the computer
Reasons for Multi-Threading
• Multitasking threads require less overhead than multitasking
processes. Processes are heavyweight tasks that require their
own separate address spaces.
• Interprocess communication is expensive and limited.
Context switching from one process to another is also costly.
• Threads, on the other hand, are lighter weight. They share the
same address space and cooperatively share the same
heavyweight process. Interthread communication is
inexpensive, and context switching from one thread to the
next is lower in cost.
• While Java programs make use of process-based multitasking
environments, process-based multitasking is not under Java’s
control. However, multithreaded multitasking is.
Thread Lifecycle
• Thread exists in several states:
1) ready to run
2) running
3) a running thread can be suspended
4) a suspended thread can be resumed
5) a thread can be blocked when waiting for a resource
6) a thread can be terminated
• Once terminated, a thread cannot be resumed.
Thread Lifecycle
sleep(500)
Active
wake up
JVM
Born start() suspend()
resume()
Runnable
Blocked
stop() wait
stop()
notify
block on I/O
Dead I/O available
Thread Lifecycle
• New state – After the creations of Thread instance the thread is in this
state but before the start() method invocation. At this point, the thread
is considered not alive.
• Runnable (Ready-to-run) state – A thread start its life from Runnable
state. A thread first enters runnable state after the invoking of start()
method but a thread can return to this state after either running,
waiting, sleeping or coming back from blocked state also. On this state a
thread is waiting for a turn on the processor.
• Running state – A thread is in running state that means the thread is
currently executing. There are several ways to enter in Runnable state
but there is only one way to enter in Running state: the scheduler select
a thread from runnable pool.
• Dead state – A thread can be considered dead when its run() method
completes. If any thread comes on this state that means it cannot ever
run again.
• Blocked - A thread can enter in this state because of waiting the
resources that are hold by another thread.
CREATING THREAD
Creating a Thread
• 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:
– You can implement the Runnable interface.
– You can extend the Thread class, itself.
• When a Java program starts up, one thread
begins running immediately. This is usually called
the main thread of our program because it is the
one that is executed when our program begins.
Implementing Runnable
• The easiest way to create a thread is to create a
class that implements the Runnable interface.
Runnable abstracts a unit of executable code.
• 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.
• The only difference is that run( ) establishes
the entry point for another, concurrent thread
of execution within your program. 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.
• Thread defines several constructors. The one that
we will use is shown here:
Thread(Runnable threadOb, String 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( ) executes a call to run( ).
// Create a second thread.
class NewThread implements Runnable {
Thread t;
NewThread() {
// Create a new, second thread
t = new Thread(this, "Demo Thread");
System.out.println("Child thread: " + t);
t.start(); // Start the thread
}
try {
for(int i = 5; i > 0; i--) {
System.out.println("Main Thread: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread exiting.");
}
}
• Inside NewThread’s constructor, a new Thread
object is created by the following statement:
t = new Thread(this, "Demo Thread");
• Passing this as the first argument indicates that you
want the new thread to call the run( ) method on this
object.
• Next, start( ) is called, which starts the thread of
execution beginning at the run( ) method. This causes
the child thread’s for loop to begin.
• After calling start( ), NewThread’s constructor returns to
main( ).
• When the main thread resumes, it enters its for loop.
Both threads continue running, sharing the CPU in single
core systems, until their loops finish.
• The output produced by this program is as
follows. (Your output may vary based upon the
specific execution environment.)
Thread Class Methods
//This is the entry point for the newly created thread – a five-iterations loop
//with a half-second pause between the iterations all within try/catch:
public void run() {
try {
for (int i = 5; i > 0; i--) {
System.out.println("Child Thread: " + i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
System.out.println("Child interrupted.");
}
System.out.println("Exiting child thread.");
}
}
Example: New Thread 3
class ThreadDemo {
public static void main(String args[]) {
//A new thread is created as an object of
// NewThread:
new NewThread();
//After calling the NewThread start method,
// control returns here.
Example: New Thread 4
//Both threads (new and main) continue concurrently.
//Here is the loop for the main thread:
try {
for (int i = 5; i > 0; i--) {
System.out.println("Main Thread: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread exiting.");
}
}
New Thread: By Extending Thread class
New Thread: By Extending Thread class
• There are two steps:
1. create a new class that extends Thread and
override run( ) method available in Thread
class. This method provides an entry point
for the thread and you will put your
complete business logic inside this method.
2. Create Thread object and you can start it by
calling start() method, which executes a call
to run( ) method.
Example 1
Example 2
Example 3
Example 3
Example 3
Daemon Threads
Thread Groups
• Every Java thread is a member of a thread group.
• Thread groups provide a mechanism for collecting multiple threads into a
single object and manipulating those threads all at once, rather than
individually.
• For example, you can start or suspend all the threads within a group with a
single method call.
• Java thread groups are implemented by the “ThreadGroup” class in the
java.lang package.
• The runtime system puts a thread into a thread group during thread
construction.
• When you create a thread, you can either allow the runtime system to put the
new thread in some reasonable default group or you can explicitly set the
new thread's group.
• The thread is a permanent member of whatever thread group it joins upon its
creation--you cannot move a thread to a new group after the thread has been
created
The ThreadGroup Class
• The “ThreadGroup” class manages groups of threads for Java
applications.
• A ThreadGroup can contain any number of threads.
• The threads in a group are generally related in some way, such
as who created them, what function they perform, or when
they should be started and stopped.
• ThreadGroups can contain not only threads but also other
ThreadGroups.
• The top-most thread group in a Java application is the thread
group named main.
• You can create threads and thread groups in the main group.
• You can also create threads and thread groups in subgroups of
main.
Creating a Thread Explicitly in a Group
• A thread is a permanent member of whatever thread group it joins when its
created--you cannot move a thread to a new group after the thread has been
created.
• Thus, if you wish to put your new thread in a thread group other than the
default, you must specify the thread group explicitly when you create the
thread.
• The Thread class has three constructors that let you set a new thread's group:
NewThread() {
// Create a new, second thread
super("Demo Thread");
System.out.println("Child thread: " + this);
start(); // Start the thread
}
try {
for(int i = 5; i > 0; i--) {
System.out.println("Main Thread: " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread exiting.");
}
}
• This program generates the same output as
the preceding version.
• As you can see, the child thread is created by
instantiating an object of NewThread, which is
derived from Thread.
• Notice the call to super( ) inside NewThread.
This invokes the following form of the Thread
constructor:
public Thread(String threadName)
Thread or Runnable
• The Thread class defines several methods that can be overridden by a
derived class. Of these methods, the only one that must be overridden
is run( ).
• This is, of course, the same method required when you implement
Runnable. Many Java programmers feel that classes should be
extended only when they are being enhanced or modified in some way.
• So, if you will not be overriding any of Thread’s other methods, it is
probably best simply to implement Runnable.
• Also, by implementing Runnable, your thread class does not need to
inherit Thread, making it free to inherit a different class.
• Ultimately, which approach to use is up to you. However, throughout
the rest of this chapter, we will create threads by using classes that
implement Runnable.
CREATING MULTIPLE THREADS
Creating Multiple Threads
• So far, you have been using only two threads:
the main thread and one child thread.
// Create multiple threads.
class NewThread implements Runnable {
String name; // name of thread
Thread t;
NewThread(String threadname) {
name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
t.start(); // Start the thread
}
try {
// wait for other threads to end
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
}
NewThread(String threadname) {
name = threadname;
t = new Thread(this, name);
System.out.println("New thread: " + t);
t.start(); // Start the thread
}
62
SYNCHRONIZATION
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. A
thread that owns a monitor can reenter the same monitor if it so
desires.
Threads: Synchronization
• How to prevent two threads from
simultaneously writing and reading the same
object?
• Java implementation of monitors:
1) classes can define so-called synchronized
methods
2) each object has its own implicit monitor
that is automatically entered when one of the
object’s synchronized methods is called
3) once a thread is inside a synchronized
method, no other thread can call any other
synchronized method on the same object
Thread Synchronization
1 0 newBalance = bank.getBalance() + 1;
2 0 newBalance = bank.getBalance() + 1;
3 1 bank.setBalance(newBalance);
4 1 bank.setBalance(newBalance);
66
Example: Showing Resource Conflict
• Objective: Write a program that demonstrates the problem of resource
conflict.
• Suppose that you create and launch one hundred threads, each of which
adds a penny to an account. Assume that the account is initially empty.
java.lang.Runnable
-char token
100 1 1 1
AddAPennyTask
+getToken AccountWithoutSync Account
+setToken
+paintComponet -bank: Account -balance: int
+mouseClicked
+run(): void -thread: Thread[]
+getBalance(): int
+deposit(amount: int): void
+main(args: String[]): void
AccountWithoutSync
67
Race Condition
What, then, caused the error in the example? Here is a possible scenario:
Step balance Task 1 Task 2
1 0 newBalance = balance + 1;
2 0 newBalance = balance + 1;
3 1 balance = newBalance;
4 1 balance = newBalance;
);
68
Race Condition
Step balance Task 1 Task 2
1 0 newBalance = balance + 1;
2 0 newBalance = balance + 1;
3 1 balance = newBalance;
4 1 balance = newBalance;
);
69
The synchronized keyword
• To avoid race conditions, more than one thread must be prevented
from simultaneously entering certain part of the program, known as
critical region.
• The critical region is the entire deposit method.
• There are several ways to correct the problem
– One approach is to make Account thread-safe by adding the
synchronized keyword in the deposit method in Line 45 as
follows:
public synchronized void deposit(double amount)
– Use the synchronized keyword to synchronize the method so
that only one thread can access the method at a time.
70
Synchronizing Instance Methods &
Static Methods
71
Synchronizing Instance Methods &
Static Methods
• With the deposit method synchronized, the preceding scenario
cannot happen.
• If Task 2 starts to enter the method, and Task 1 is already in the
method, Task 2 is blocked until Task 1 finishes the method.
Task 1 Task 2
Acquire a-char
locktoken
on the object account -char token
+getToken +getToken
-char token +setToken +setToken
+paintComponet +paintComponet
Execute
+getToken the deposit method
+mouseClicked +mouseClicked
+setToken
+paintComponet Wait to acquire the lock
-char token
+mouseClicked
-char token
+getToken Release the lock
+setToken
+getToken
+paintComponet
-char token Acqurie a lock
+setToken on the object account
+mouseClicked
+paintComponet
+getToken -char token
+mouseClicked
+setToken
+paintComponet Execute the deposit method
+getToken
+mouseClicked +setToken
+paintComponet
-char token
+mouseClicked
+getToken Release the lock
+setToken
+paintComponet
72
Synchronizing Statements
• Invoking a synchronized instance method of an object acquires a
lock on the object, and invoking a synchronized static method of a
class acquires a lock on the class.
• A synchronized statement can be used to acquire a lock on any
object, not just this object, when executing a block of the code in a
method.
• This block is referred to as a synchronized block. The general form of
a synchronized statement is as follows:
synchronized (expr) {
statements;
}
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
while(true) {
q.put(i++);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
class PC {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
} catch(InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
valueSet = true;
System.out.println("Put: " + n);
notify();
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
while(true) {
q.put(i++);
}
}
}
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
try {
Thread.sleep(1000);
} catch(Exception e) {
System.out.println("A Interrupted");
}
try {
Thread.sleep(1000);
} catch(Exception e) {
System.out.println("B Interrupted");
}
Deadlock() {
Thread.currentThread().setName("MainThread");
Thread t = new Thread(this, "RacingThread");
t.start();