关于java:并发王者课青铜5一探究竟如何从synchronized理解Java对象头中的锁

2次阅读

共计 5042 个字符,预计需要花费 13 分钟才能阅读完成。

在后面的文章《青铜 4:synchronized 用法初体验》中,咱们曾经提到 的概念,并指出 synchronized 是锁机制的一种实现。可是,这么说未免太过形象,你可能无奈直观地了解 锁到底是什么 ?所以,本文会粗略地介绍synchronized 背地的一些基本原理,让你对 Java 中的锁有个粗略但直观的印象。

本文将分两个局部,首先你要从 Mark Word 中意识锁,因为对象锁的信息存在于 Mark Word 中,其次通过 JOL 工具理论体验 Mark Word 的变动。

一、从 Mark Word 意识锁

咱们晓得,在 HotSpot 虚拟机中,一个对象的存储散布由 3 个局部组成:

  • 对象头(Header):由 Mark WordKlass Pointer组成;
  • 实例数据(Instance Data):对象的成员变量及数据;
  • 对齐填充(Padding):对齐填充的字节,临时不用理睬。

在这 3 个局部中,对象头中的 Mark Word 是本文的重点,也是了解 Java 锁的要害。Mark Word 记录的是对象运行时的数据,其中包含:

  • 哈希码(identity_hashcode)
  • GC 分代年龄(age)
  • 锁状态标记
  • 线程持有的锁
  • 偏差线程 ID(thread)

所以,从对象头中的 Mark Word 看,Java 中的锁就是对象头中的一种数据 。在 JVM 中, 每个对象都有这样的锁,并且用于多线程拜访对象时的并发管制。

如果一个线程想拜访某个对象的实例,那么这个线程必须领有该对象的 。首先,它须要通过对象头中的 Mark Word 判断该对象的实例是否曾经被线程锁定。如果没有锁定,那么线程会在 Mark Word 中写入一些标记数据,就是通知他人:这个对象是我的啦!如果其余线程想拜访这个实例的话,就须要进入期待队列,直到以后的线程开释对象的锁,也就是把 Mark Word 中的数据擦除。

当一个线程领有了锁之后,它便能够屡次进入。当然,在这个线程开释锁的时候,那么也须要执行雷同次数的开释动作。比方,一个线程先后 3 次取得了锁,那么它也须要开释 3 次,其余线程才能够持续拜访。

上面的表格展现的是 64 位计算机中的对象头信息:

|------------------------------------------------------------------------------------------------------------|--------------------|
|                                            Object Header (128 bits)                                        |        State       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                  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   |       Normal       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |    OOP to metadata object   |       Biased       |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 |    OOP to metadata object   | Lightweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 |    OOP to metadata object   | Heavyweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
|                                                                     | lock:2 |    OOP to metadata object   |    Marked for GC   |
|------------------------------------------------------------------------------|-----------------------------|--------------------|

从表格中,你能够看到 Object Header 中的三局部信息:Mark Word、Klass Word、State.

二、通过 JOL 体验 Mark Word 的变动

为了直观感触对象头中 Mark Word 的变动,咱们能够通过 JOL(Java Object Layout) 工具演示一遍。JOL 是一个不错的 Java 内存布局查看工具,心愿你能记住它。

首先,在工程中引入依赖:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

在上面的代码中,master是咱们创立的对象实例,办法 decreaseBlood() 中会执行加锁动作。所以,在调用 decreaseBlood() 加锁后,对象头信息应该会发生变化

 public static void main(String[] args) {Master master = new Master();
        System.out.println("==== 加锁前 ====");
        System.out.println(ClassLayout.parseInstance(master).toPrintable());
        System.out.println("==== 加锁后 ====");
        synchronized (master) {System.out.println(ClassLayout.parseInstance(master).toPrintable());
        }
    }

后果输入如下:

==== 加锁前 ====
cn.tao.king.juc.execises1.Master object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    int Master.blood                              100
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

==== 加锁后 ====
cn.tao.king.juc.execises1.Master object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           48 f9 d6 00 (01001000 11111001 11010110 00000000) (14088520)
      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    int Master.blood                              95
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total


Process finished with exit code 0

从后果中能够看到,代码在执行 synchronized 办法后,所打印出的 object header 信息由 01 00 00 0000 00 00 00 变成了 48 f9 d6 0000 70 00 00 等等,不出意外的话,置信你应该看不明确这些内容的含意。

所以,为了不便浏览,咱们在青铜系列文章《借花献佛 -JOL 格式化工具》中提供了一个工具类,让输入更具可读性。借助工具类,咱们把代码调整为:

 public static void main(String[] args) {Master master = new Master();
        System.out.println("==== 加锁前 ====");
        printObjectHeader(master);
        System.out.println("==== 加锁后 ====");
        synchronized (master) {printObjectHeader(master);
        }
    }

输入的后果如下:

==== 加锁前 ====
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
Class Pointer: 11111000 00000000 11000001 01000011 
Mark Word:
    hashcode (31bit): 0000000 00000000 00000000 00000000 
    age (4bit): 0000
    biasedLockFlag (1bit): 0
    LockFlag (2bit): 01

==== 加锁后 ====
Class Pointer: 11111000 00000000 11000001 01000011 
Mark Word:
    javaThread*(62bit,include zero padding): 00000000 00000000 01110000 00000000 00000100 11100100 11101001 100100
    LockFlag (2bit): 00

你看,这样一来,输入的后果的后果就高深莫测。从 加锁后 的后果中能够看到,Mark Word 曾经发生变化,以后线程曾经取得对象的锁。

至此,你应该明确,原来 synchronized 的背地的原理是这么回事 。当然,本文所讲述只是其中的局部。出于篇幅思考和难度管制, 本文暂且不会对 Java 对象头中锁的含意和锁的降级等问题开展形容,这部分内容会在前面的文章中具体介绍。

以上就是文本的全部内容,祝贺你又上了一颗星✨

夫子的试炼

  • 下载 JOL 工具,在代码中体验工具的应用和对象信息的变动。

对于作者

关注公众号【庸人技术笑谈】,获取及时文章更新。记录平凡人的技术故事,分享有品质(尽量)的技术文章,偶然也聊聊生存和现实。不贩卖焦虑,不抛售课程。

如果本文对你有帮忙,欢送 点赞 关注

正文完
 0