Multithreading and Thread Safety in Java
Multithreading and Thread Safety
Multithreading is the process of executing two or more threads concurrently in a single program. Single core processor can execute one thread at a time but OS uses the time slicing feature to share processor time. Thread is a light weight process. The thread exists in the process and requires less resources to create. It shares the process resources. When the java application starts, it creates the first user thread for main which in turn spawn’s multiple user threads and daemon thread. The thread scheduler schedules the thread execution. When the work of all the threads are done, the JVM terminates the application.
Every java application has at least one thread – main thread. Although, there are so many other java threads running in background like memory management, system management, signal processing etc. But from application point of view – main is the first java thread and we can create multiple threads from it.
Some important points about threads:
-
- Thread share their parent process data and code.
- Context switching in thread is less expensive.
- Thread intercommunication is relatively easy than process.
No of ways to create thread:
There are 2 ways to create a thread
- By Extending Thread Class
- Using Runnable Interface
1. By extending Thread class
1 2 3 4 5 6 |
public class Task extends Thread{ @Override public void run() { System.out.println("In run method: "+ Thread.currentThread().getName()); } } |
2. By implementing Runnable interface
1 2 3 4 5 6 |
public class Task implements Runnable{ @Override public void run() { System.out.println("In run method: "+ Thread.currentThread().getName()); } } |
Runnable vs Thread
If the class provides more functionalities rather than just running as thread then we should implement Runnable interface as java supports implementing multiple interfaces. But if the class only goal is to run as thread then we can extend Thread class.
It’s advisable to create a thread by implementing Runnable interface because if you extend thread class you cannot extend other class. Thread doesn’t return any value to the caller, if we want to do the same then we should use java callable feature.
Thread life cycle/ Thread states:
- New: When a thread is created using new operator, then the thread is not alive and its state is internal to java programming. The thread scheduler doesn’t know about it.
- Runnable: When the thread start method is invoked then the thread is put to runnable state. Then the control is given to the thread scheduler to run the thread instantly or keep it in runnable thread pool before running.
- Running: When the thread is executing its state is changes from runnable to running state. The thread scheduler picks the thread from runnable thread pool for executing. The running thread can be put to blocked, dead or runnable state based on scheduler.
- Blocked: A thread can be blocked/ waiting for the other thread to finish execution or it can be waiting for the resources to be available.
- Dead: Once the thread finish execution then the thread is moved to dead state.
Method available under Thread
1. Thread Sleep
Thread sleep method can be used to pause the execution of current thread for the specified amount of time in milliseconds.
Thread.sleep(1000);
// pauses the execution for 1 second.
Calling the sleep method, the thread will not lose any monitors or locks it has acquired. Any other thread can interrupt the current thread in sleep, in that case InterruptedException
is thrown.
The thread.sleep method interacts with the thread scheduler to put it to the waiting state for specified time once the time is over, the thread state is changed to runnable state and wait for the CPU for further execution. So the actual time that current thread sleep depends on the thread scheduler that is part of operating system.
2. Thread Join
The join
method is used to pause the execution of the current thread until the thread it has joined has completed execution/dead.
Following are the overloaded join methods in thread class:
1 |
public final void join() |
Current thread will be put to waiting state until the thread it has joined has completed execution.
1 |
public final synchronized void join(long millis) |
Current thread will be put to waiting state until the thread it has joined has completed execution or for the specified amount of time. Again the time the current thread is put to wait state depends on the scheduler.
1 |
public final synchronized void join(long millis, int nanos) |
Current thread will be put to waiting state until the thread it has joined has completed execution or for the specified amount of time in millis and nanos. Again the time the current thread is put to wait state depends on the scheduler.
Synchronization methods
The object class in java has three methods Wait, Notify, NotifyAll
to communicate about the lock status of a shared resources. The current thread invoking these method on the object should have object monitor else it will throw IllegalMonitorStateEception
.
Calling wait()
on a the current executing thread puts it into Blocked state and it releases the monitor it holds.
In Java in order to enter critical section of code, Threads needs lock and they wait for lock, they don’t know which threads holds lock instead they just know the lock is hold by some thread and they should wait for lock instead of knowing which thread is inside the synchronized block and asking them to release lock. this analogy fits with wait and notify being on object class rather than thread in Java.
If wait()
and notify()
were on the Thread instead then each thread would have to know the status of every other thread. How would thread1 know that thread2 was waiting for access to a particular resource? If thread1 needed to call thread2.notify()
it would have to somehow find out that thread2 was waiting. There would need to be some mechanism for threads to register the resources or actions that they need so others could signal them when stuff was ready or available.
Below is the usage of wait and notify methods to solve famous producer and consumer problem.
Message.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Message { private String msg; public Message(String msg) { this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } |
Producer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Producer implements Runnable{ Message message; public Producer(Message message) { this.message = message; } @Override public void run() { for(int i=0; i<5; i++) synchronized(message){ try { message.setMsg("hi"); System.out.println(Thread.currentThread().getName() + " " +message.getMsg()); message.notifyAll(); message.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } |
Consumer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Consumer implements Runnable{ Message message; public Consumer(Message message){ this.message = message; } @Override public void run() { for(int i=0; i<5; i++) synchronized (message) { try { message.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ " "+message.getMsg()); message.notifyAll(); } } } |
WaitNotifyTest.java
1 2 3 4 5 6 7 8 9 10 11 12 |
public class WaitNotifyTest { public static void main(String[] args) { Message message = new Message("proces"); Consumer consumer = new Consumer(message); new Thread(consumer,"consumer").start(); Producer producer = new Producer(message); new Thread(producer,"producer").start(); } } |
Output:
1 2 3 4 5 6 7 8 9 10 |
producer hi consumer hi producer hi consumer hi producer hi consumer hi producer hi consumer hi producer hi consumer hi |
Thread Safety
Thread Safety is the process to make the program safe to use under multithreaded environment.
Ways to make the code thread safe:
- Synchronization
- Atomic wrapper classes
- Thread safe collection classes
- Volatile keyword – to read data from memory and not from thread cache,
Synchronization
provides data integrity on the cost of performance, so it should be used only when it’s absolutely necessary. Synchronization works only in the same JVM, for multiple jvm environment use some global locking mechanism. Java synchronized keyword cannot be used for constructors and variables.
It is preferable to create a dummy private Object to use for synchronized block, so that it’s reference can’t be changed by any other code. For example if you have a setter method for Object on which you are synchronizing, it’s reference can be changed by some other code leads to parallel execution of the synchronized block.
1 2 3 4 5 6 7 8 9 10 |
public class MyObject { public Object lock = new Object(); public void doSomething() { synchronized (lock) { //... } } } |
Read about monitor uses in java Multithreading
We should not use any object that is maintained in a constant pool for locking/synchronizing, for example String should not be used for synchronization because if any other code is also locking on same String, it will try to acquire lock on the same reference object from String pool and even though both the codes are unrelated, they will lock each other.
Happy Learning!!