025 synchronized 不同情况下的对象头测试

synchronized 不同情况下的对象头测试

测试环境

JDK:Oracle JDK 1.8.0_144

代码依赖:

  • junit-jupiter-engine:5.8.1
  • slf4j-simple:1.7.32
  • jol-core:0.16

测试代码

import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.openjdk.jol.info.ClassLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class LockObject {}

class SyncTest {

    private static final Logger log = LoggerFactory.getLogger(SyncTest.class);

    @Test
  void testSynchronizedLock() throws InterruptedException {
    Object lock = new Object();
    syncLock(lock);
    Assertions.assertTrue(true);
  }

  void syncLock(Object lock) {
    log.info("加锁前 {}", ClassLayout.parseInstance(lock).toPrintable());
    synchronized (lock) {
        log.info("加锁中 {}", ClassLayout.parseInstance(lock).toPrintable());
    }
    log.info("加锁后 {}", ClassLayout.parseInstance(lock).toPrintable());
  }
}

测试情况

这里通过改动 testSynchronizedLock 方法代码进行测试,下面的测试情况只说明改动后的 testSynchronizedLock的代码,其余代码不再说明。因为只关注对象头的变化,其余的值也省略了。

情况一:同线程直接调用

void testSynchronizedLock() throws InterruptedException {
    Object lock = new Object();
    syncLock(lock);
    Assertions.assertTrue(true);
}

执行结果为:

加锁前 0x0000000000000001 (non-biasable; age: 0)
加锁中 0x0000700007830f10 (thin lock: 0x0000700007830f10)
加锁后 0x0000000000000001 (non-biasable; age: 0)

通过结果可以看到,加锁前的对象头是 0x0000000000000001,加锁中是 0x0000700007830f10,加锁后是 0x0000000000000001。看着可能不太明白,这里简单说下 64 位 jvm 的对象头的分布情况

|--------------------------------------------------------------------------------------------------------------|
|                                              Object Header (128 bits)                                        |
|--------------------------------------------------------------------------------------------------------------|
|                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |       
|--------------------------------------------------------------------------------------------------------------|
|  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  无锁
|----------------------------------------------------------------------|--------|------------------------------|
|  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                                                                      | lock:2 |     OOP to metadata object   |  GC
|--------------------------------------------------------------------------------------------------------------|

lock: 锁状态标记位,该标记的值不同,整个mark word表示的含义不同。

biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

从分布可以得出,看锁标记,直接看后 3 位即可

biased_lock lock 16进制 状态
0 01 1 无锁
1 01 5 偏向
0 00 0 轻量
0 10 2 重量
0 11 3 GC

加锁前的对象头是 0x0000000000000001,加锁中是 0x0000700007830f10,加锁后是 0x0000000000000001

从这种情况可以看出:加锁前对象处于无锁状态,加锁中处于轻量锁状态,释放锁后处于无锁状态

这种现象和我们想象的可能不太一样,在网上找了资料如下:

JVM启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等等。在这个过程中会使用大量 synchronized 关键字对对象加锁,且这些锁大多数都不是偏向锁。为了减少初始化时间,JVM默认延时加载偏向锁。这个延时的时间大概为 4s 左右,具体时间因机器而异。当然我们也可以设置 JVM 参数 -XX:BiasedLockingStartupDelay=0 来取消延时加载偏向锁。

从上面可以看出,JVM默认延时加载偏向锁,时间大于 4s,为了更好的验证,下面的代码直接按 10s 处理。

情况二:先获取一次锁,然后延迟 10s 再次获取

这个主要为了验证一下上面的结论

void testSynchronizedLock() throws InterruptedException {
    Object lock = new Object();
    syncLock(lock);
    TimeUnit.SECONDS.sleep(10);
    syncLock(lock);
    Assertions.assertTrue(true);
}

日志输出如下:

加锁前 0x0000000000000001 (non-biasable; age: 0)
加锁中 0x00007000028aaf10 (thin lock: 0x00007000028aaf10)
加锁后 0x0000000000000001 (non-biasable; age: 0)

加锁前 0x0000000000000001 (non-biasable; age: 0)
加锁中 0x00007000028aaf10 (thin lock: 0x00007000028aaf10)
加锁后 0x0000000000000001 (non-biasable; age: 0)

两次获取锁都使用的轻量级锁

情况三:延迟 10s 后在创建锁对象后调用

void testSynchronizedLock() throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    Assertions.assertTrue(true);
}

日志如下:

加锁前 0x0000000000000005 (biasable; age: 0)
加锁中 0x00007fb114010805 (biased: 0x0000001fec450042; epoch: 0; age: 0)
加锁后 0x00007fb114010805 (biased: 0x0000001fec450042; epoch: 0; age: 0)

从这种情况可以看出:加锁前对象处于偏向锁状态,加锁中处于偏向锁状态,释放锁后处于偏向锁状态,不过在加锁前,并没有偏向任何线程

情况四:增加 BiasedLockingStartupDelay=0 参数

void testSynchronizedLock() throws InterruptedException {
  Object lock = new Object();
  syncLock(lock);
  Assertions.assertTrue(true);
}

日志

加锁前 0x0000000000000005 (biasable; age: 0)
加锁中 0x00007fd650009005 (biased: 0x0000001ff5940024; epoch: 0; age: 0)
加锁后 0x00007fd650009005 (biased: 0x0000001ff5940024; epoch: 0; age: 0)

从这种情况可以看出:加锁前对象处于偏向锁状态,加锁中处于偏向锁状态,释放锁后处于偏向锁状态,不过在加锁前,并没有偏向任何线程

从上述四种情况可以得出:

默认情况 JVM 会延迟启动偏向锁功能,在 JVM 启用偏向锁功能前创建的锁对象,直接使用轻量级锁开始获取锁,而不会通过轻量级锁阶段。如果关闭延迟功能,可以使用 -XX:BiasedLockingStartupDelay=0 参数

后面的测试情况使用 TimeUnit.SECONDS.sleep(10); 来实现和 -XX:BiasedLockingStartupDelay=0 的效果

情况五:同线程多次调用

void testSynchronizedLock() throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    syncLock(lock);
    Assertions.assertTrue(true);
}

日志如下:

加锁前 0x0000000000000005 (biasable; age: 0)
加锁中 0x0000023099602005 (biased: 0x000000008c265808; epoch: 0; age: 0)
加锁后 0x0000023099602005 (biased: 0x000000008c265808; epoch: 0; age: 0)

加锁前 0x0000023099602005 (biased: 0x000000008c265808; epoch: 0; age: 0)
加锁中  0x0000023099602005 (biased: 0x000000008c265808; epoch: 0; age: 0)
加锁后 0x0000023099602005 (biased: 0x000000008c265808; epoch: 0; age: 0)

从日志可以看出,第一次加锁时,使用的偏向锁,加锁后偏向于 0x000000008c265808 第二次加锁时,因为还在同一线程内,偏向锁指向还是一样,则直接获取锁,不进行锁升级。

情况六:多线程无竞争两次调用

void testSynchronizedLock() throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    Thread thread = new Thread(() -> syncLock(lock));
    thread.start();
    thread.join();
    Assertions.assertTrue(true);
}

日志如下:

加锁前 0x0000000000000005 (biasable; age: 0)
加锁中 0x00000264e4573005 (biased: 0x00000000993915cc; epoch: 0; age: 0)
加锁后  0x00000264e4573005 (biased: 0x00000000993915cc; epoch: 0; age: 0)

加锁前 0x00000264e4573005 (biased: 0x00000000993915cc; epoch: 0; age: 0)
加锁中 0x000000d0c6dff748 (thin lock: 0x000000d0c6dff748)
加锁后 0x0000000000000001 (non-biasable; age: 0)

从日志可以看出,第一次加锁时,使用的偏向锁,第二次加锁时使用的轻量级锁(8的二进制时 1000),从中可以得出,即使没有竞争关系,只要有一个线程加过锁,那另一个线程再加锁就会变成轻量级锁,从最后一次日志可以看出,最终又变成了无锁状态

情况七:多线程无竞争很多次调用

void testSynchronizedLock() throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    Thread thread = new Thread(() -> syncLock(lock));
    thread.start();
    thread.join();
    syncLock(lock);
    thread = new Thread(() -> syncLock(lock));
    thread.start();
    thread.join();
    Assertions.assertTrue(true);
}

日志

加锁前 0x0000000000000005 (biasable; age: 0)
加锁中  0x0000028212c72005 (biased: 0x00000000a084b1c8; epoch: 0; age: 0)
加锁后 0x0000028212c72005 (biased: 0x00000000a084b1c8; epoch: 0; age: 0)

加锁前 0x0000028212c72005 (biased: 0x00000000a084b1c8; epoch: 0; age: 0)
加锁中 0x000000ca803fefd8 (thin lock: 0x000000ca803fefd8)
加锁后 0x0000000000000001 (non-biasable; age: 0)

加锁前 0x0000000000000001 (non-biasable; age: 0)
加锁中 0x000000cafeefc8a0 (thin lock: 0x000000cafeefc8a0)
加锁后 0x0000000000000001 (non-biasable; age: 0)

加锁前 0x0000000000000001 (non-biasable; age: 0)
加锁中  0x000000ca803ff308 (thin lock: 0x000000ca803ff308)
加锁后 0x0000000000000001 (non-biasable; age: 0)

结果就是验证了,轻量级锁是可以转换成无锁的

情况八:多线程有竞争调用

void testSynchronizedLock() throws InterruptedException {
    TimeUnit.SECONDS.sleep(10);
    Object lock = new Object();
    syncLock(lock);
    Thread thread = new Thread(() -> syncLock(lock));
    Thread thread2 = new Thread(() -> syncLock(lock));
    thread.start();
    thread2.start();

    thread.join();
    thread2.join();
    Assertions.assertTrue(true);
}

void syncLock(Object lock) {
    log.info("currentThread {}", Thread.currentThread().getId());
    log.info("加锁前 {}", ClassLayout.parseInstance(lock).toPrintable());
    synchronized (lock) {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("加锁中 {}", ClassLayout.parseInstance(lock).toPrintable());
    }
    log.info("加锁后 {}", ClassLayout.parseInstance(lock).toPrintable());
}

日志如下:

加锁前 0x0000000000000005 (biasable; age: 0)
加锁中 0x000001b16e421005 (biased: 0x000000006c5b9084; epoch: 0; age: 0)
加锁后  0x000001b16e421005 (biased: 0x000000006c5b9084; epoch: 0; age: 0)

加锁前 0x000001b16e421005 (biased: 0x000000006c5b9084; epoch: 0; age: 0)
加锁前 0x000001b16e421005 (biased: 0x000000006c5b9084; epoch: 0; age: 0)
加锁中 0x000001b10b4f0fba (fat lock: 0x000001b10b4f0fba)
加锁后 0x000001b10b4f0fba (fat lock: 0x000001b10b4f0fba)
加锁中 0x000001b10b4f0fba (fat lock: 0x000001b10b4f0fba)
加锁后 0x000001b10b4f0fba (fat lock: 0x000001b10b4f0fba)

从日志可以看出,显示偏向,然后是重量级锁,最后没有变成无锁

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容