Thread vs Process:
- Process actually does not run but threads run. So, internally (in the process) there will be at least one thread which will be doing the actual task/work.
- Threads (of the same process) run in a shared memory space, while processes run in separate memory spaces.
- Thread is termed as 'light weight process', since it is similar to a real process but executes within the context of a process and shares the same resources (address space/process memory, data) allotted to the process by the kernel.
Main Thread:
- JVM starts the main thread.
- This is the first thread that is created after Java application gets started.
- It is the thread from which other 'child' threads will be spawned.
- It must be the last thread to finish execution bcz it performs various shutdown actions.
Thread class: (in java.lang package)
- states: available as enums in Thread class.
Image: represents the state diagram
- A thread is in the blocked state, if it tries to access a protected section of code that is currently locked by some other thread. Whenever thr protected section is unblocked, the scheduler picks one thread (from blocked states) and moves that thread to runnable state from the blocked state.
- A thread is in the waiting state when it waits for another thread on a condition, when this condition is fulfilled, the scheduler is notified and waiting thread is moved to runnable state.
t.getState() : method to get the current state.
Synchronized Method vs Block:
Synchronized Block :
- reduces the scope of the lock
- we can use arbitrary any lock(object) for mutual exclusion.
- can throw NullPointerException if the lock expression is null.
Synchronized Method:
- Entire method will be synchronized.
- lock will be on 'this' object or on the class (if the method is static)
- will not be null, so will not throw NullPointerException.
Inter-thread Communication:
object lock == monitor
- Communication happens via 3 ways: (wait(), notify() or notifyAll())
- These methods are called from the synchronized context (otherwise IllegalMonitorStateException will be thrown if a thread has been instructed to wait for an object's monitor that the specified thread does not have ownership of.)
wait() method: (defined in Object class)
- Can be called on the object, on which the current thread has lock.
- Necessary to have lock on that object.
- Thread will release the lock on the object and will go in wait state.
- Should call wait() method inside while loop instead of if block bcz it's possible that thread wakes up spuriously even the waiting condition is not changed.
Race Condition:
majorly occurs in 2 situations:
1. Check and act
2. Read modify write
(The problem here is our assumption that each line of code is atomic, BUT it is NOT).
ex: '++' operator is not an atomic operation.
- Check and Act:
Ex1:
if(!hashTable.contains(key)){
hashTable.put(key, value);
}
Here, both the operations (contains() and put()) are atomic individually but are not atomic together.
So, here race condition can occur, if 2 threads checks the same result of contains() method.
Ex2:
public Singleton getInstance(){
if(_instance == null){
_instance = new Singleton();
}
}
Here also, if 2 threads read _instance's value as null -> both the threads will enter inside if block. Same problem will occur.
- Read Update Write:
i++ is not an atomic operation.
so, if not synchronized then race condition will occur here.
OR
we can use AtomicInteger, which is internally thread-safe. Then no need to synchronized externally.
Thread Safety:
- Immutable objects are thread-safe.
- Readonly or final variables are thread-safe.
- Local variables are thread-safe bcz each thread has there own copy.
- Vector, concurrentHashMap, HashTable, String classes are thread-safe classes.
Dead Lock:
when 2 or more threads are waiting for each other to release the resource they need (lock) and get stuck for infinite time.
ex:
public void method1(){
synchronized(String.class){
synchronized(Integer.class){
System.out.println("acquired both the locks and reached here");
}
}
}
public void method2{
synchronized(Integer.class){
synchronized(String.class){
System.out.println("acquired both the locks and reached here");
}
}
}
Here, a good chance is present of deadlock.
Solution: should acquire locks in same order.
Important Methods inside Thread class:
1. join() method:
- final method
- nonstatic method
Ex: if we have 3 threads: T1, T2, T3
need to make sure that initially T1 will complete it's task then T2 will complete and then T3 will complete.
In this requirement, we can use join() method.
t1.join(); // from T2
t2.join(); // from T3
Ex:
Thread exThread = new Thread() {
public void run() {
try{
System.out.println(Thread.currentThread().getName() + "started");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "ended");
}catch(InterruptedException e){
System.out.println("Exception caught");
}
}};
exThread.start();
exThread.join();
System.out.println(Thread.currentThread().getName() + "ended");
O/P:
Thread-0 started
Thread-0 ended
main ended
2. sleep() method:
- static method
- pauses the current thread for specified miliseconds.
- does not release the lock.
Other ways to pause the current thread:
TimeUnit.SECONDS.sleep(4);
TimeUnit.MINUTES.sleep(4);
TimeUnit.HOURS.sleep(2);
TimeUnit.DAYS.sleep(1);
-> above are available in java.util.concurrent package (since Java 5).
3. yield() method:
- static method
- current thread goes from running state to runnable state and gives chance to another thread to go into running state.
- So, after calling yield() method scheduler checks if there is any thread with same or higher priority than this thread. If found -> then it will move current thread to read/runnable state and give processor to other thread.
Please comment down any query/doubt regarding the topic, will try to clarify that ASAP.