The purpose of multithreading is to execute multiple thread in
parallel or sequence as per the problem requirement.
So next question is what is thread? Thread is a light weight process that has its
own call stack.
In java there is one thread per call stack—or, to think in inverse
one call stack per thread.
The main method that start the program runs in one thread, is
called main thread.
We can define and instantiate a thread in two ways
1. By extending
the java.lang.Thread class
2. By Implement
the Runnable interface
Code snippet -
/**
* Creating thread via extending
java.lang.Thread class
* @author MANOJ
*/
public class TestThread extends Thread {
public void run() {
System.out.println("thread is running...");
}
public static void main(String args[]) {
TestThread testThread = new TestThread();
testThread.start();
}
}
/**
* Creating thread via extending
java.lang.Runnable Inteface
* @author MANOJ
*/
public class TestRunnable implements Runnable {
public void run() {
System.out.println("thread is running...");
}
public static void main(String args[]) {
TestRunnable testRunnable = new TestRunnable();
Thread thread = new Thread(testRunnable);
thread.start();
}
}
As per the multiple
inheritance limitation in java classes we prefer to use runnable interface for
thread creation
Thread State and Transition –
A thread can be in 5 states.
1.
New - In this
state a thread has been created, but the start method has not been invoked on
the thread.
2.
Runnable- In this state
thread is eligible to run but it’s waiting for the scheduler. Thread enter
runnable state when start() executed or it’s coming back from blocked, waiting
or sleeping state.
3.
Running - In this state
the thread scheduler select the runnable thread from runnable pool to be the
current executing process.
4.
Waiting/blocked/sleeping
– In this state thread is not eligible to run and
not in runnable state but if a particular condition or event occur it will come
back to runnable state.to achieve this state we can call sleep() or yield()
method.
5.
Dead – In this state
thread execution is done and run() method completes. Once a thread is dead, it
can never be brought back to life. When we call start() method on dead thread
it will throw a runtime exception(java.lang.IllegalThreadStateException)
Thread Priorities and yield()
–
Thread always run with some priority usually represented as a
number between 1 to 10. The scheduler in most of the JVMs uses preemptive,
priority based scheduling(which implies some sort of time slicing).
If a thread enters the runnable state and it has a higher priority
than any of the thread in the pool and higher priority than the currently
running thread. The lower priority running thread usually will be bumped back to
runnable and highest priority thread will be chosen to run.
But don’t rely on thread priority when designing multithreaded
application because thread scheduling priority behavior is not guaranteed,
thread priority can be used to improve the efficiency of program, but not
surety.
Yield() – to promote
graceful turn taking among equal priority thread, but no guarantee.
The join() method – it
make sure that 2nd thread will not start until tha first thread has
finished.
Code Snippet –
/**
* Creating thread via extending
java.lang.Runnable Inteface
* Using join() method threadTwo will start
when threadOne completed
* @author MANOJ
*/
public class TestRunnable implements Runnable {
public void run() {
System.out.println(Thread.currentThread()+" is running...");
}
public static void main(String args[]) {
TestRunnable testRunnable = new TestRunnable();
Thread threadOne = new Thread(testRunnable, "threadOne");
Thread threadTwo = new Thread(testRunnable, "threadTwo");
threadOne.start();
try {
threadOne.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread two start to run");
threadTwo.start();
}
}
Inter thread Interaction –
Inter thread communication
is mechanism in which a thread communicate with other thread and release the
resources as per the thread requirement.
For thread communication we
use three methods define in object class.
wait () – It cause current
threat to releases the lock and wait for specific time period or until notify()
or notifyAll is called. Current thread must own this object so it must be
called form synchronized context.
notify () – It will wakes
up a single thread that is waiting on this object’s monitor.
notifyAll () – It will
wakes up all thread that are waiting on this object’s monitor.
Code snippet –
/**
* Creating thread via extending
java.lang.Runnable Inteface
* Using wait() and notify() method for inter-thread communication
* these method must call from synchronized
context otherwise will
* throw runtime exception
java.lang.IllegalMonitorStateException
* @author MANOJ
*/
public class TestRunnable implements Runnable {
public void run() {
synchronized(this) {
System.out.println(Thread.currentThread()+" is running...");
notify();
}
}
public static void main(String args[]) {
TestRunnable testRunnable = new TestRunnable();
Thread threadOne = new Thread(testRunnable, "threadOne");
threadOne.start();
synchronized (threadOne) {
try {
threadOne.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread
communication using notifyAll()
/**
* In below code snippet three calculator
thread are waiting for calculate
* calculte is notifying all the waiting
thread via notifyAll() method
* @author Manoj
*/
public class TestCalculator {
public static void main(String[] args) {
Calculate calculate = new Calculate();
Thread calcThread = new Thread(calculate);
Thread thread1 = new Thread(new Calculator(calculate), "A");
Thread thread2 = new Thread(new Calculator(calculate), "B");
Thread thread3 = new Thread(new Calculator(calculate), "C");
thread1.start();
thread2.start();
thread3.start();
calcThread.start();
}
}
class Calculator implements Runnable {
private Calculate calculate;
public Calculator(Calculate calculate) {
this.calculate = calculate;
}
public void run() {
synchronized (calculate) {
try {
System.out.println("Waiting for calculation");
calculate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(calculate.sum);
}
}
}
class Calculate implements Runnable {
int sum = 0;
public void run() {
for (int count = 0; count <= 10; count++) {
sum = sum + count;
}
synchronized (this) {
notifyAll();
}
}
}
Thread
communication example in producer consumer problem
import java.util.List;
/**
* Producer will put the value in taskQueue if
queue is full it will wait for
* consumer notification otherwise put the vale in queue and will call notify
* @author Manoj
*/
public class Producer implements Runnable {
private List<Integer> taskQueue;
private int MAX_SIZE;
Producer(List<Integer> sharedQueue, int size) {
this.taskQueue = sharedQueue;
MAX_SIZE = size;
}
public void run() {
int counter = 0;
while (true) {
try {
produce(counter++);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void produce(int taskCounter) throws InterruptedException {
synchronized (taskQueue) {
while (taskQueue.size() == MAX_SIZE) {
System.out.println("Queue is full " + Thread.currentThread().getName());
taskQueue.wait();
}
Thread.sleep(1000);
taskQueue.add(taskCounter);
System.out.println("Produced: " + taskCounter);
taskQueue.notify();
}
}
}
-------------------------------------------------------------------------------------------------------------------------------
import java.util.List;
/**
* Consumer will consume the value from
taskQueue if queue is empty it will wait for
* producer notification otherwise consume the vale in queue and will call notify
* @author Manoj
*/
public class Consumer implements Runnable {
private List<Integer> taskQueue;
Consumer(List<Integer> sharedQueue) {
this.taskQueue = sharedQueue;
}
public void run() {
while (true) {
try {
consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void consume() throws InterruptedException {
synchronized (taskQueue) {
while (taskQueue.isEmpty()) {
System.out.println("Queue is empty " + Thread.currentThread().getName());
taskQueue.wait();
}
Thread.sleep(1000);
System.out.println("Consumed: " + taskQueue.remove(0));
taskQueue.notify();
}
}
}
-------------------------------------------------------------------------------------
public class TestProdCons {
public static void main(String[] args) {
List<Integer> taskQueue = new ArrayList<>();
Thread prodThread = new Thread(new Producer(taskQueue, 5), "Producer");
Thread consThread = new Thread(new Consumer(taskQueue), "Consumer");
prodThread.start();
consThread.start();
}
}
Synchronization–
Synchronization provide mutual exclusion on shared
resource. It provide locking and unlocking mechanism on shared object.
Synchronized keyword can be used in code block or in a method.
1. When
a thread enters into java synchronized method or blocks it acquires a lock and
when it leaves java synchronized method or block it releases the lock
2. Java
Thread acquires an object level lock when it enters into an instance
synchronized java method and acquires a class level lock when it enters into
static synchronized java method
3. Java
synchronized keyword is re-entrant in nature it means if a java synchronized method
calls another synchronized method which requires the same lock then the current
thread which is holding lock can enter into that method without acquiring the
lock.
4. You
can’t use Java synchronized keyword with constructor it will give illegal
modifier compiler error.
Code Snippet –
Object1.method1()
thread 1 executing synchronized method1()
Object1.method2()
thread 2 is waiting for
Synchronization is built around an internal entity
known as the intrinsic lock or monitor lock.
So if a thread executing a synchronized block on an
object than other thread can’t execute the any other synchronized block on that
object but get lock from a different object.
If a thread executing static synchronized method
than no other thread can get lock for any synchronized method of that class.
Volatile variable –
Volatile variable guarantees that value of the
variable will always be read from main memory and not from thread local cache
Volatile variable reduces the risk of memory consistency.
Use of volatile variable in Singleton
public class Singleton {
private static volatile Singleton _instance;
public static Singleton getInstance() {
if (_instance == null) {
synchronized (Singleton.class) {
if (_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
}
In above example if we don’t use volatile variable
than memory inconsistency problem will occur
Suppose if a thread1 execute this class it will
check if –instance is null it will acquire the lock and execute the synchronize
block and at same time thread2 executed, but thread1 is not completed it just
allocated the memory for _instance but object is not created completed in this
situation thread2 will find that _instance is not null and it will get the
partially created object.
Great Post
ReplyDeleteThanks Vikash
ReplyDeleteDouble checking algorithm may still fail in creation of Singleton object, as the second thread may still see the _instance as null at line no. 5 & 7 and try creating second object.
ReplyDeleteThanks mack,
ReplyDeleteSecond thread will not come inside synchronized block until lock releases by first thread. and on line no 5 it will find null and will wait for the lock, once the lock release by the first thread it will come inside the synchronized block and will check the null again on line 7 but till this point instance will be created by first thread, so it will not create the new instance.
Nice information, very usefull thanks
ReplyDeleteGood Questions with nice explanations
ReplyDelete