在前面的文章《青铜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工具,在代码中体验工具的使用和对象信息的变化。

关于作者

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

如果本文对你有帮助,欢迎点赞关注

并发王者课-青铜5:一探究竟-如何从synchronized理解Java对象头中的锁的更多相关文章

  1. 并发王者课 - 青铜4:synchronized用法初体验

    在前面的文章<双刃剑-理解多线程带来的安全问题>中,我们提到了多线程情况下存在的线程安全问题.本文将以这个问题为背景,介绍如何通过使用synchronized关键字解这一问题.当然,在青铜 ...

  2. 并发王者课 - 青铜 2:峡谷笔记 - 简单认识Java中的线程

    在前面的<兵分三路:如何创建多线程>文章中,我们已经通过Thread和Runnable直观地了解如何在Java中创建一个线程,相信你已经有了一定的体感.在本篇文章中,我们将基于前面的示例代 ...

  3. 并发王者课-青铜7:顺藤摸瓜-如何从synchronized中的锁认识Monitor

    在前面的文章中,我们已经体验过synchronized的用法,并对锁的概念和原理做了简单的介绍.然而,你可能已经察觉到,有一个概念似乎总是和synchronized.锁这两个概念如影相随,很多人也比较 ...

  4. 并发王者课-铂金6:青出于蓝-Condition如何把等待与通知玩出新花样

    欢迎来到<[并发王者课](https://juejin.cn/post/6967277362455150628)>,本文是该系列文章中的**第19篇**. 在上一篇文章中,我们介绍了阻塞队 ...

  5. 并发王者课-铂金2:豁然开朗-“晦涩难懂”的ReadWriteLock竟如此妙不可言

    欢迎来到<并发王者课>,本文是该系列文章中的第15篇. 在上篇文章中,我们介绍了Java中锁的基础Lock接口.在本文中,我们将介绍Java中锁的另外一个重要的基本型接口,即ReadWri ...

  6. 并发王者课-铂金1:探本溯源-为何说Lock接口是Java中锁的基础

    欢迎来到<并发王者课>,本文是该系列文章中的第14篇. 在黄金系列中,我们介绍了并发中一些问题,比如死锁.活锁.线程饥饿等问题.在并发编程中,这些问题无疑都是需要解决的.所以,在铂金系列文 ...

  7. 并发王者课-铂金8:峡谷幽会-看CyclicBarrier如何跨越重峦叠嶂

    欢迎来到<并发王者课>,本文是该系列文章中的第21篇,铂金中的第8篇. 在上一篇文章中,我们介绍了CountDownLatch的用法.在协调多线程的开始和结束时,CountDownLatc ...

  8. 并发王者课-铂金9:互通有无-Exchanger如何完成线程间的数据交换

    欢迎来到<并发王者课>,本文是该系列文章中的第22篇,铂金中的第9篇. 在前面的文章中,我们已经介绍了ReentrantLock,CountDownLatch,CyclicBarrier, ...

  9. 并发王者课-铂金10:能工巧匠-ThreadLocal如何为线程打造私有数据空间

    欢迎来到<并发王者课>,本文是该系列文章中的第23篇,铂金中的第10篇. 说起ThreadLocal,相信你对它的名字一定不陌生.在并发编程中,它有着较高的出场率,并且也是面试中的高频面试 ...

随机推荐

  1. Java(171-194)【接口、多态】

    1.接口概述与生活举例 接口就是一种公共的规范标准 只要符合规范标准,就可以大家通用 2.接口的定义基本格式  public interface 接口名称 {       // 抽象方法      / ...

  2. 弦图及其在 OI 中的现代应用

    八月份的时候得知要填综评表格,综评表格里面又需要一个研究性学习报告,而我连研究性学习课的老师长啥样都不知道.于是我把两份 OI 笔记拼拼凑凑成了这篇文章充当两份研究性学习报告之一(另一份可能更有趣一些 ...

  3. manjaro 手动调节屏幕亮度

    1 问题描述 manjaro版本20.0,桌面XFCE,设置之类的地方没有屏幕亮度调节的功能. 2 解决方案 解决方案来自arch wiki. 亮度由ACPI内核模块控制,这个模块的接口在以下位置: ...

  4. 字符串转成KB,MB, GB

    import java.text.DecimalFormat; public class SizeUtil { public static String GetImageSize(String ima ...

  5. java中的|=符号

    今天看mybaits源码的时候,发现了这个特殊的符号用法,|=. for (ParameterMapping pm : boundSql.getParameterMappings()) { Strin ...

  6. Win10双系统安装Gentoo-(附Networkmanagr/Gnome安装)

    安装Gentoo 本文主要参考的是官方Wiki,还有一些网友的经验,在后面的具体安装步骤里会分享对应网友的一些解决办法和相关文章 官方Wiki:https://wiki.gentoo.org/wiki ...

  7. 邮件功能 - yagmail 模块

    简介 使用 yagmail 模块可以更简单地实现邮件发送功能. 安装:pip install yagmail 代码示例 1 import yagmail 2 3 def send_mail(repor ...

  8. Python容器相关操作

    (集合与字典除外)的容器相关操作 (1)容器的拼接 >>> 'abc' + 'def' 'abcdef' (2)容器的重复 >>> (1, 2) * 3 (1, 2 ...

  9. 这一篇文章帮你搞定Java(含Java全套资源)

    当下想学习Java开发的人越来越多,对于很多零基础的人来说,没有相关的视频教程及相关的学习线路,学起来是一件很费劲的事情,还有很多人从网上及其它渠道购买视频,这些视频资料的价格对于刚毕业的大学生来说也 ...

  10. 让我们一起建设 Vue DevUI 项目吧!🥳

    DevUI Design 是从华为云 DevCloud 众多业务孵化出来的一套设计体系,DevUI 倡导沉浸.灵活.至简的设计价值观,提倡设计者为真实的需求服务,为多数人进行设计,拒绝哗众取宠.取悦眼 ...