If you are familiar with thread dump or at least looked at it couple of times, probably you would have noticed WAITING & WAITING (parking) states. So what’s the difference?
In Java, we have Object.wait() and Unsafe.park() methods. Both of them will suspend the running thread & put it in waiting state. But these two methods work on different principles. Object.wait() results in WAITING state whereas Unsafe.park() method results in WAITING (parking) state.
To understand the difference, let’s first understand the concept of two things.
Monitor: It is a construct which works on lock. Monitor can acquire a lock or give up an already acquired lock. It can also listen to a particular lock & get notification when it is released.
Happens-before relationship:
Take a look at the picture above. We have multicore CPUs nowadays. Each CPU core can work as standalone processing unit. Each CPU has its own cache where it can put frequently accessed data. Main memory or RAM is common for all CPU cores. Now JVM will utilize multiple cores when multiple threads are used for concurrency. Let’s take an example scenario.
- Thread 1 uses core 1.
- Thread 2 uses core 2.
- Thread 1 & thread 2 share a common variable.
- Core 1 will have its own copy of the variable in cache which is used by thread 1.
- Same way, core 2 will have its own copy of the variable in its own CPU cache for thread 2.
- Now thread 1 updates the variable & core 1 updates its own cache.
- Thread 2 then reads the variable. But it will read from core 2 CPU cache. So thread 2 will get stale value.
The problem is that one CPU core doesn’t know that copy of the same variable is getting used in another CPU core. They are updating their own copy of the data & be happy with it. To avoid this, Java uses happens-before relationship. The problem here is that thread 1 didn’t update the value in main memory & thread 2 didn’t fetch the value from main memory before reading. Happens-before relationship makes sure that updated value gets synced to main memory before thread 1 releases the lock. Also thread 2 syncs the value from main memory after it acquires the lock. But this comes with additional overhead.
Object.wait():
Object.wait() method works on monitor based synchronization. And it satisfies happens-before relationship in Java.
Object obj = new Object();
synchronized(obj){ // monitor acquires lock on object
try {
obj.wait();
} catch(InterruptedException e){
e.printStackTrace();
}
}
Before we call a wait() method on an object, monitor needs to acquire a lock on that object. We have created a object name obj above. Monitor acquires a lock on obj in second line. Now we call wait() method on obj object. Below things happen under the hood.
- Monitor releases the lock on obj object.
- Thread goes to waiting state.
- Monitor keeps on listening to locking related events on obj object.
How to bring back thread from waiting to running state:
synchronized(lock){
lock.notify();
}
Monitor in another thread acquires lock on obj object. It calls notify() method on obj. When notify() is called, below things happen under the hood:
- lock on obj is released from the other thread.
- Monitor in first thread gets notification that lock is released on obj object.
- Monitor in first thread now acquires the lock back on obj & brings back the thread in running state.
Unsafe.park()
sun.misc.Unsafe unsafe = null;
try {
java.lang.reflect.Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (sun.misc.Unsafe) f.get(null);
} catch(NoSuchFieldException | IllegalAccessException e){
e.printStackTrace();
}
unsafe.park(true, 0L);
unsafe.park() method puts the thread in waiting state. But it works on permit basis, not on monitor basis. It doesn’t satisfy happens-before relationship. You can consider permit as a counter. When park() is called, counter is incremented. Thread is put to WAITING (parking) state.
How to bring back thread from waiting(parking) to running state:
sun.misc.Unsafe unsafe = null;
try {
java.lang.reflect.Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (sun.misc.Unsafe) f.get(null);
} catch(NoSuchFieldException | IllegalAccessException e){
e.printStackTrace();
}
unsafe.unpark(thread);
To decrement the counter unsafe.unpark() method needs to be called from another thread. unpark() takes a thread object as argument. Once counter becomes zero, the waiting thread passed as method argument is brought back to running state. If unpark() is called before park() method, next park() method will get unblocked immediately.
Comparison:
Unsafe.park() method should give better performance as it uses native code & no synchronization is required with main memory. That’s why thread pool in general (for example ExecutorService) use park method while waiting for tasks from the blocking queue. But Unsafe class is JVM specific which uses native code. As an application developer you should avoid it. Also it doesn’t guarantee happens before relationship which is required almost all the times. So stick to Object.wait().