理论上这是可能的,但需要一些额外的条件和特殊的 JVM 设置。
理论
对于某些对象,偏向锁定显然是无利可图的,例如涉及两个或多个线程的生产者-消费者队列。这样的对象必然有锁争用。另一方面,在某些情况下,将一组对象重新偏置到另一个线程的能力是有利可图的,特别是当一个线程分配许多对象并对每个对象执行初始同步操作,但另一个线程对它们执行后续工作时,例如示例基于弹簧的应用程序。
JVM 试图同时涵盖这两个用例并支持 rebaising 和 revocation。请参阅使用偏置锁定和批量重新偏置消除与同步相关的原子操作中的详细说明
换句话说,您的理解:
据我了解,JVM 最初会将锁偏向 A,然后在线程 B 第一次唤醒时撤销偏向。
并非总是如此,即 JVM 足够聪明,可以检测到无竞争的同步并将锁重新偏向另一个线程。
以下是一些实现说明:
- HotSpot 仅支持批量重新偏置以摊销每个对象偏置撤销的成本,同时保留优化的好处。
- bulk rebias 和 bulk revoke 共享一个安全点/操作名称 - RevokeBias。这非常令人困惑,需要额外的调查。
- 当且仅当撤销次数多于
BiasedLockingBulkRebiasThreshold
和少于BiasedLockingBulkRevokeThreshold
且最新撤销不迟于时,后续的批量 rebias 是可能BiasedLockingDecayTime
的,其中所有转义变量都是 JVM 属性。请仔细阅读此代码。
- 您可以使用 property 跟踪安全点事件
-XX:+PrintSafepointStatistics
。最有趣的是 EnableBiasedLocking、RevokeBias 和 BulkRevokeBias
-XX:+TraceBiasedLocking
生成一个有趣的日志,其中包含有关 JVM 决策的详细描述。
实践
这是我的复制器,其中一个线程(实际上是主线程)分配监视器对象并对其执行初始同步操作,然后另一个线程执行后续工作:
package samples;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static java.lang.System.out;
public class BiasLocking {
private static final Unsafe U;
private static final long OFFSET = 0L;
static {
try {
Field unsafe = Unsafe.class.getDeclaredField("theUnsafe");
unsafe.setAccessible(true);
U = (Unsafe) unsafe.get(null);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public static void main(String[] args) throws Exception {
ExecutorService thread = Executors.newSingleThreadExecutor();
for (int i = 0; i < 15; i++) {
final Monitor a = new Monitor();
synchronized (a) {
out.println("Main thread \t\t" + printHeader(a));
}
thread.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
synchronized (a) {
out.println("Work thread \t\t" + printHeader(a));
}
return null;
}
}).get();
}
thread.shutdown();
}
private static String printHeader(Object a) {
int word = U.getInt(a, OFFSET);
return Integer.toHexString(word);
}
private static class Monitor {
// mutex object
}
}
为了重现我的结果,请使用以下 JVM 参数:
- -XX:+UseBiasedLocking - 不需要,默认使用
- -XX:BiasedLockingStartupDelay=0 - 默认有延迟4s
- -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 - 启用安全点日志
- -XX:+TraceBiasedLocking - 非常有用的日志
- -XX:BiasedLockingBulkRebiasThreshold=1 - 在我的示例中减少迭代次数
在测试过程中,JVM 决定重新启动监视器而不是撤销
Main thread 0x7f5af4008805 <-- this is object's header word contains thread id
* Beginning bulk revocation (kind == rebias) because of object 0x00000000d75631d0 , mark 0x00007f5af4008805 , type samples.BiasLocking$Monitor
* Ending bulk revocation
Rebiased object toward thread 0x00007f5af415d800
vmop [threads: total initially_running wait_to_block] [time: spin block sync cleanup vmop] page_trap_count
0.316: BulkRevokeBias [ 10 0 0 ] [ 0 0 0 0 0 ] 0
Work thread 0x7f5af415d905 <-- this is object's header word contains thread id => biased
下一步是将锁定重新偏向主线程。这部分是最难的,因为我们必须达到以下启发式:
Klass* k = o->klass();
jlong cur_time = os::javaTimeMillis();
jlong last_bulk_revocation_time = k->last_biased_lock_bulk_revocation_time();
int revocation_count = k->biased_lock_revocation_count();
if ((revocation_count >= BiasedLockingBulkRebiasThreshold) &&
(revocation_count < BiasedLockingBulkRevokeThreshold) &&
(last_bulk_revocation_time != 0) &&
(cur_time - last_bulk_revocation_time >= BiasedLockingDecayTime)) {
// This is the first revocation we've seen in a while of an
// object of this type since the last time we performed a bulk
// rebiasing operation. The application is allocating objects in
// bulk which are biased toward a thread and then handing them
// off to another thread. We can cope with this allocation
// pattern via the bulk rebiasing mechanism so we reset the
// klass's revocation count rather than allow it to increase
// monotonically. If we see the need to perform another bulk
// rebias operation later, we will, and if subsequently we see
// many more revocation operations in a short period of time we
// will completely disable biasing for this type.
k->set_biased_lock_revocation_count(0);
revocation_count = 0;
}
您可以使用 JVM 参数和我的示例来达到此启发式方法,但请记住,这非常困难,有时需要 JVM 调试。