This question is about the one of the heuristics Java uses of biased locking. The next paragraph is for future readers; I suspect anyone who can answer this question can safely skip it.
As far as I understand, once upon a time, people noticed that Java has lots of classes that are thread safe, but whose instances tend to be used by only one thread, so Sun introduced biased locking to take advantage of that. The trouble is, if you "guess wrong" and try to bias a lock that needs to be used from two threads, the bias needs to be undone ("revoked") even if there is no contention, and this is so expensive that the JVM tries hard to avoid it, even if it means sometimes missing out on situations where biased locking could have been a net win.
I also know that sometimes the JVM decides the do a "bulk" re-bias, and migrate many all locks of a certain type to a different thread. This question is NOT about that. For the purpose of this question, suppose I only have two threads and a single lock. (The real situation is more complicated and involves thread pools, but let's ignore that for now. Really, pretend I didn't mention it.) Suppose further that Thread A runs an infinite loop something along the lines of "sleep for a few seconds, increment integer under lock, repeat". (It's not really that useless, but this should be enough to get the point across.) Meanwhile, Thread B runs a similar loop, but the sleep time is several hours instead of a few seconds. Suppose further that the scheduler is magical and guarantees that there is never any contention. (Preemptive nitpicking: we could just a volatile if that were true. This is just an example. Work with me here.) This assumption is unrealistic, but I'm trying to worry about just one thing at a time.
Now, suppose we care about the average latency between thread A waking up and successfully incrementing its integer. As far as I understand, the JVM would initially bias the lock towards A, and then revoke the bias the first time that thread B woke up.
My question is: would the JVM ever realize that its initial guess was basically correct, and thus re-bias the lock towards Thread A again?
In theory it is possible but requires a few additional conditions and special JVM settings.
Theory
There are certain objects for which biased locking is obviously unprofitable, such as producer-consumer queues where two or more threads are involved. Such objects necessarily have lock contention. On the other hand there are situations in which the ability to rebias a set of objects to another thread is profitable, in particular when one thread allocates many objects and performs an initial synchronization operation on each, but another thread performs subsequent work on them, for example spring based applications.
JVM tries to cover both use cases and supports rebaising and revocation at the same time. See detailed explanation in Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing
In other words your understanding:
is not always true i.e. JVM is smart enough to detect uncontended synchronization and rebias the lock towards to another thread.
Here is some implementation notes:
BiasedLockingBulkRebiasThreshold
and less thanBiasedLockingBulkRevokeThreshold
and the latest revoke was not later thanBiasedLockingDecayTime
, where all escaped variables are JVM properties. Please read carefully this code.-XX:+PrintSafepointStatistics
. The most interesting are EnableBiasedLocking, RevokeBias and BulkRevokeBias-XX:+TraceBiasedLocking
produces an interesting log with detailed descriptions about JVM decisions.Practice
Here is my reproducer, where one thread(actually main thread) allocates monitor object and performs an initial synchronization operation on it, then another thread performs subsequent work:
In order to reproduce my results please use the following JVM arguments:
In the middle of test JVM decides to rebaise monitor instead of revocation
The next step is to rebias the lock towards to the main thread. This part is the hardest one because we have to hit the following heuristics:
You can play with JVM parameters and my example to hit this heuristics, but keep in mind, it is very hard and sometimes requires a JVM debugging.