Unit 5 Notes

Download as pdf or txt
Download as pdf or txt
You are on page 1of 16

Thread in Java

In Java, a Thread is a lightweight process that allows a program to


operate more efficiently by running multiple threads in parallel.
Internally, JVM creates a Thread and hands it over to the operating
system for execution.
The operating system then schedules, executes this thread and
performs various state transitions between multiple threads. During
state transitions and life cycle, a Thread goes into various states
depending on several factors such as thread priority, forcibly
suspending a thread or waiting for the output of blocking operations.
Another benefit of using thread is that if a thread gets an exception
or an error at the time of its execution, it doesn't affect the execution
of the other threads. All the threads share a common memory and
have their own stack, local variables and program counter. When
multiple threads are executed in parallel at the same time, this
process is known as Multithreading.
Thread Life Cycle States
A Java thread can be in any of the following thread states during its
life cycle:
• New
• Runnable (or Running)
• Blocked
• Waiting
• Timed Waiting
• Terminated
These are also called life cycle events of a thread.
New : As soon as, you create new thread, it’s
in NEW state. Thread remains in New state until the program starts
the thread using its start() method. At this point, the thread is not
alive.
Runnable : Calling the thread.start() method puts the thread
in RUNNABLE state. At this point, execution control is passed to OS
thread scheduler to finish its execution. The thread scheduler decide
from this point that this thread should be executed (also known as
dispatching the thread) or should be put on hold to give chance to
other runnable threads. Thread scheduling is platform dependent —
the behavior of a multi-threaded program could vary across different
Java implementations.

Blocked : 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. A blocked thread
cannot use a processor, even if one is available.
Waiting : Usually program put a thread in WAIT state because
something else needs to be done prior to what current thread is
doing. A thread can be put in waiting state using
• object.wait()
• thread.join() or
• LockSupport.park()
Once the thread wait state is over, its state is changed
to RUNNABLE and it is moved back to thread pool.
Timed Waiting : A RUNNABLE thread can transition to the TIMED
WAITING state if it provides an optional wait interval when it’s
waiting for another thread to perform a task. You can put a java
thread in TIMED WAITING state by calling using following methods:
• thread.sleep(long millis)
• wait(int timeout) or wait(int timeout, int nanos)
• thread.join(long millis)
Such a thread returns to the RUNNABLE state when it is notified by
another thread or when the timed interval expires—whichever
comes first. Timed waiting threads and waiting threads cannot use a
processor, even if one is available.
Terminated : A thread enters the TERMINATED state (sometimes
called the dead state) when it successfully completes its task or
otherwise terminated due to any error or even it was forcefully killed.
How to create a thread in Java
There are two ways for creating a thread in Java: by extending
the Thread class; and by implementing the Runnable interface. Both
are in the java.lang package so you don’t have to use import
statement.Then you put the code that needs to be executed in a
separate thread inside the run() method which is overridden from
the Thread/Runnable. And invoke the start() method on
a Thread object to put the thread into running status (alive).
Extending the Thread class example:
public class ThreadExample1 extends Thread {
public void run() {
System.out.println("My name is: " + getName());
}
public static void main(String[] args) {
ThreadExample1 t1 = new ThreadExample1();
t1.start();
System.out.println("My name is: " +
Thread.currentThread().getName());
}
}
You see that the ThreadExample1 class extends the Thread class and
overrides the run() method. Inside the run() method, it simply prints
a message that includes the name of the thread, which is returned
from the getName() method of the Thread class.
And now let’s see the main() method that is invoked when the
program starts. It creates an instance of the ThreadExample1 class
and call its start() method to put the thread into running state. And
the last line prints a message that includes the name of the main
thread - every Java program is started from a thread called main. The
static method currentThread() returns the Thread object associated
with the current thread.
Run this program and you will see the output as follows:
My name is: Thread-0
My name is: main
You see, there are actually 2 threads:
- Thread-0: is the name of the thread we created.
- main: is the name of the main thread that starts the Java program.
The thread Thread-0 terminates as soon as its run() method runs to
complete, and the thread main terminates after the main() method
completes its execution.
One interesting point is that, if you run this program again for several
times, you will see sometimes the thread Thread-0 runs first,
sometimes the thread main runs first. This can be recognized by the
order of thread names in the output changes randomly. That means
there’s no guarantee of which thread runs first as they are both
started concurrently. You should bear in mind this behavior with
regard to multi-threading context.
Implementing Runnable Interface example:
public class ThreadExample2 implements Runnable {
public void run() {
System.out.println("My name is: " +
Thread.currentThread().getName());
}
public static void main(String[] args) {
Runnable task = new ThreadExample2();
Thread t2 = new Thread(task);
t2.start();
System.out.println("My name is: " +
Thread.currentThread().getName());
}
}

there’s a small difference as compared to the previous program: An


object of type Runnable (the ThreadExample2 class) is created and
passed to the constructor of a Thread object (t2).
The Runnable object can be viewed as a task which is separated from
the thread that executes the task.
Extending the Thread class can be used for simple cases. It cannot
be used if your class needs to extend another class because Java
doesn’t allow multiple inheritances of class.
Implementing the Runnable interface is more flexible as Java allows
a class can both extend another class and implement one or more
interfaces.
Thread Priority in Java
In a Java multithreading environment, every thread has a priority
which is an integer value between 1 and 10 where 1 is the lowest and
10 is the highest. Thread priority is an essential concept since the
scheduler picks up the threads for execution based on the priority.
The thread scheduler gives preference to higher priority threads. This
means it executes the high priority threads first and then the low
priority threads. It also depends on the JVM implementation. If there
are multiple threads with different priorities, it uses preemptive
scheduling based on priority. Suppose if threads have the same
priorities, then it may use the FCFS concept or waiting time.
The Thread class has 3 constant variables that define the thread
priorities. We can assign priorities for the user threads else the
system automatically assigns default values.
• MIN_PRIORITY: This is the minimum priority with a value of 1.
• NORM_PRIORITY: This is the default normal priority with a value of 5.
• MAX_PRIORITY: This is the maximum priority with a value of 10.
We can get and set the priority values for threads in Java using
the getPriority() and setPriority() methods. Based on these priorities,
the thread scheduler decides the order of thread execution. We
should also remember that there is no guarantee that the scheduler
always executes the high priority threads first since it also depends
on the JVM implementation.
Example:
public class setThreadPriority extends Thread {
public void run() {
System.out.println("Thread " + Thread.currentThread().getName()
+ " started");
System.out.println("Thread with priority " +
Thread.currentThread().getPriority() + " is running");
}
public static void main(String[] args) {
setThreadPriority t1 = new setThreadPriority();
setThreadPriority t2 = new setThreadPriority();
setThreadPriority t3 = new setThreadPriority();
t1.setPriority(3);
t2.setPriority(9);
t3.setPriority(1);
t1.start();
t2.start();
t3.start();
}
}
OUTPUT:-
Thread Thread-0 started
Thread Thread-1 started
Thread Thread-2 started
Thread with priority 9 is running
Thread with priority 3 is running
Thread with priority 1 is running
Multithreading
Multithreading is a programming technique where multiple threads,
or lightweight sub-processes, run concurrently within a single
process. Each thread has its own execution path and can perform
tasks independently while sharing the same resources, such as
memory and file handles, with other threads in the same process.
Benefits of Multithreading in Java
Multithreading in Java offers several advantages:
• Improved Performance: Multithreaded programs can take full
advantage of multi-core processors, making them faster and
more efficient.
• Enhanced Responsiveness: Multithreading allows for
responsive user interfaces (UIs) in applications. Long-running
tasks can be moved to background threads, ensuring that the UI
remains responsive to user input.
• Resource Utilization: Multithreading enables better utilization
of system resources, as different threads can execute different
tasks simultaneously.
• Parallel Processing: Multithreading is essential for parallel
processing, where a large task is divided into smaller sub-tasks
that can be executed concurrently, reducing overall execution
time.
Example of Multi thread:
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running with priority " +
Thread.currentThread().getPriority());
}
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.setPriority(Thread.MIN_PRIORITY); // Set priority to 1
thread2.setPriority(Thread.MAX_PRIORITY); // Set priority to 10
thread1.start();
thread2.start();
}
}
Synchronization in Java Multithreading
Synchronization in Java is a powerful mechanism that allows you to
control the access of multiple threads to shared resources. It ensures
that only one thread can access the resource at a time, preventing
data inconsistency and race conditions. This is essential in a
multithreading environment where threads often share resources like
variables, arrays, or objects.
In Java, the synchronized keyword can be used to synchronize access
to critical sections of code. There are two main types of
synchronization:
• Synchronized Methods: Entire methods are marked as
synchronized, ensuring that only one thread can execute them
at a time.
• Synchronized Blocks: Specific blocks of code are marked as
synchronized, providing more granular control over the
synchronization.

Synchronized Methods : A synchronized method ensures that only


one thread can execute it at a time for a particular object. The lock is
held on the object instance on which the method is called.

Syntax:

public synchronized void synchronizedMethod() {


// synchronized code
}

Synchronized Blocks : A synchronized block is a block of code within


a method that is synchronized on a specified object. This allows you
to lock only the critical section of the code rather than the entire
method.
Syntax:
public void method() {
synchronized (this) {
// synchronized code
}
}
Static Synchronization : Static synchronization ensures that a class-
level lock is acquired, so all instances of the class share the same
lock.
Syntax:
public static synchronized void staticSynchronizedMethod() {
// synchronized code
}

Or using a synchronized block:


public static void staticMethod() {
synchronized (ClassName.class) {
// synchronized code
}
}

Example: Synchronized Method


class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedMethodExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount());
}
}

Output:
Final count: 2000

Explanation:
• The Counter class has a synchronized increment method.
• Two threads are created and both execute
the increment method 1000 times each.
• The final count is 2000, demonstrating that the synchronization
prevents race conditions.

Example: Synchronized Block


class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
public class SynchronizedBlockExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount());
}
}

Output:
Final count: 2000

Explanation:
• The Counter class has an increment method with a
synchronized block.
• Two threads are created and both execute
the increment method 1000 times each.
• The final count is 2000, demonstrating that the synchronization
prevents race conditions.

Java Interthread Communication


Inter thread communication in Java or cooperation is the process of
coordinating the communication between the synchronized threads.
This means, when a new thread wants to enter the critical section, it
pauses the currently executing thread to allow the new thread. In
this way, if the synchronized threads cooperate with each other, it
reduces the risk of a deadlock situation. We can implement inter-
thread communication using the wait, notify, and notifyAll methods
of the Object class.

wait method
The wait() method pauses the current thread execution and waits
until the time elapses or another thread invokes the notify() or
notifyAll() method.
We can invoke the wait() method only within the synchronized
method or synchronized block since the thread must own the
monitor, else it will throw an exception. Once it calls the wait()
method, it releases the current lock monitor.
public final void wait(long milliseconds) throws InterruptedException

What is a monitor
Whenever a synchronized thread wants to access a shared resource,
it acquires a lock and enters the monitor. At a time, only one thread
can own a monitor. The other threads need to wait until the first
thread releases the monitor.

notify method
The notify method awakes the thread that called the wait method on
the same resource. This causes the thread that was sleeping to
resume its execution. If there were multiple threads in the wait state
for the same shared resource, then it notifies any one of them.
public final void notify()

notifyAll method
The notifyAll method awakes all the threads that call the wait
method on the same resource. If there are multiple threads, then it
wakes up the highest priority thread.
public final void notifyAll()

Example of inter-thread communication in Java


class Stock {
int qty = 15;
synchronized void purchaseStock(int quantity) {
System.out.println("Purchase stock");
if(this.qty < quantity) {
System.out.println("Insufficient quantity available");
try {
wait();
}
catch(Exception e) {
e.printStackTrace();
}
}
this.qty = this.qty - quantity;
System.out.println("Purchase stock completed");
}
synchronized void addStock(int quantity) {
System.out.println("Adding Product stock");
this.qty = this.qty + quantity;
System.out.println("Add stock completed");
notify();
}
}
public class InterThreadDemo {
public static void main(String[] args) {
Stock s = new Stock();
new Thread() {
public void run() {
s.purchaseStock(20);
}
}.start();
new Thread() {
public void run() {
s.addStock(10);
}
}.start();
}
}

OUTPUT:
Purchase stock
Insufficient quantity available
Adding Product stock
Add stock completed
Purchase stock completed
When the first thread calls the purchaseStock() method, it acquires
the lock and checks if the required quantity is available in the stock.
Since the requested quantity(which is 20) is lesser than the available
quantity( which is 15), it calls the wait() method.
Now it releases the lock and starts the second thread since the first
thread is in the wait state. The second thread calls the addStock()
method which adds the required quantity to the available stock and
then invokes the notify() method. The notify() method wakes up the
first thread that had called the wait() method and resumes the
execution.
Now, since the required stock is available, it decreases the requested
quantity and updates the available stock, and completes the
execution.
Difference between wait and sleep
Though wait and sleep methods perform more or less the same
operation, there are some differences as below.
wait sleep
Belongs to the Object class Belongs to the Thread class
It is a non-static method It is a static method
Wait operation is interrupted by notify or Sleep operation is interrupted after a
notifyAll methods specified timeout
It releases lock when wait method is called It does not release or own any lock

You might also like