java锁结构之无锁偏向锁轻量级锁重量级锁

article/2025/10/21 13:14:29

一、对象的本质(锁在对象中的体现)

1.1、对象的结构

  对象由多部分构成的,对象头、属性字段、补齐字段等。补齐字段是指如果对象总大小不是4字节的整数倍,会填充上一段内存地址是之成为4的整数倍。
在这里插入图片描述
其中,对象头在对象的最前端,包含两部分或者三部分:Mark Words、Klass words,如果对象是一个数组,那么还可能包含第三部分:数组的长度。
1)Klass word里面存的是一个地址,占32位或者64位,是一个指向当前对象所属于的类的地址,可以通过这个地址获取到它的元数据信息。
2)Mark Word主要包含对象的hash值、年龄分代、hashcode、锁标志位等。

ps:如何查看对象的这部分内容?
1、引入JOL依赖,具体如下图:

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

2、创建实体类

public class JOLObject {}

3、创建测试类

public class JOLExample1 {static JOLObject jolObject;public static void main(String[] args) {jolObject=new JOLObject();//打印JVM的详细信息System.out.println(VM.current().details());System.out.println("==============");//打印对应的对象头信息System.out.println(ClassLayout.parseInstance(jolObject).toPrintable());}
}

运行结果如下:

# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]==============
com.yang.java.lock.domain.JOLObject object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0

  不是说Klass是64bits(8个字节)但是这儿只有4个字节,是因为我们开启了指针压缩,我们可以关闭指针压缩看看,是不是8个字节。我们只需要使用以下的JVM运行参数

-XX:-UseCompressedOops

输出结构:

com.yang.java.lock.domain.JOLObject object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           b0 5c 20 ae (10110000 01011100 00100000 10101110) (-1373610832)12     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
  • 关于对象结构,后续会专门写一篇介绍对象结构的文章

二、锁结构为什么要升级

  首先,这四种锁结构都是针对synchronized来说的。锁的升级是由于竞争的激烈程度导致的,竞争越大,锁结构越重量化。
在这里插入图片描述
先看一段比较基本的synchronized代码

public class SynchronizedDemo2 {Object object=new Object();public void method1(){synchronized (object){}method2();}private static void method2(){}
}

通过指令javac SynchronizedDemo2.java编译生成.class文件,使用javap -verbose SynchronizedDemo2.class查看.class文件信息

在这里插入图片描述
  关注红色方框里的monitorenter和monitorexit即可。 Monitorenter和Monitorexit指令,会让对象在执行,使其锁计数器加1或者减1。每一个对象在同一时间只与一个monitor(锁)相关联,而一个monitor在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的Monitor锁的所有权的时候,monitorenter指令会发生如下3中情况之一: monitor计数器为0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待 如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加 这把锁已经被别的线程获取了,等待锁释放 monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。 下图表现了对象,对象监视器,同步队列以及执行线程状态之间的关系:
在这里插入图片描述

三、锁结构升级的实现

在这里插入图片描述
在这里插入图片描述
markword相关字段详细解释:

  1. identity_hashcode:31位的对象标识hashcode,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。(待验证)
  2. age:4位的对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1.当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6.由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
  3. biased_lock标志位:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。
  4. lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。biased_lock和lock一起,表达的锁状态含义如下:
biasedlock状态
001无锁
101偏向锁
00轻量级锁
01重量级锁
11GC标记
  1. thread: 持有偏向锁的线程ID
  2. epoch:偏向锁的时间戳
  3. ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针
  4. ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针

  我们通常说的通过synchronized实现的同步锁,真实名称叫做重量级锁。但是重量级锁会造成线程排队(串行执行),且会使CPU在用户态和核心态之间频繁切换,所以代价高、效率低。为了提高效率,不会一开始就使用重量级锁,JVM在内部会根据需要,按如下步骤进行锁的升级:

3.1、无锁->偏向锁

  初期锁对象刚创建时,还没有任何线程来竞争,对象的Mark Word是下图的第一种情形,这偏向锁标识位是0,锁状态01,说明该对象处于无锁状态(无线程竞争它)。
请添加图片描述
  当有一个线程来竞争锁时,先用偏向锁,表示锁对象偏爱这个线程,这个线程要执行这个锁关联的任何代码,不需要再做任何检查和切换,这种竞争不激烈的情况下,效率非常高。这时Mark Word会记录自己偏爱的线程的ID,把该线程当做自己的熟人。
请添加图片描述

3.2、偏向锁->轻量级锁

   当有两个线程开始竞争这个锁对象,情况发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象并执行代码,锁对象的Mark Word就执行哪个线程的栈帧中的锁记录。
请添加图片描述

3.3、轻量级锁->重量级锁

   如果竞争的这个锁对象的线程更多,导致了更多的切换和等待,JVM会把该锁对象的锁升级为重量级锁,这个就叫做同步锁,这个锁对象Mark Word再次发生变化,会指向一个监视器对象,这个监视器对象用集合的形式,来登记和管理排队的线程。
请添加图片描述

3.4、GC标记

请添加图片描述

3.5、锁是否可以越级升级

   这里产生了一个疑问:锁升级是否会出现直接由偏向锁直接转为重量级锁,跳过中间轻量级锁的过程。现在看来,应该是不会,因为锁升级的内在驱动是多个线程对锁对象的竞争从而导致锁对象中的mark word中相关字段的改写。而线程不可能突然并行执行成千上万个,因为线程可以并行执行的数量取决于CPU核心数(虽然有超线程的存在,但是也不可能超过CPU核心数太多),而对于CPU来说,是以CPU时间片分配给线程执行的,只有拿到了CPU时间片的线程才能获取到CPU的控制权。所以即使在应用层面同时创建多个线程,在线程执行层面(即CPU层面)还是依然是通过时间片的机制执行线程的,一定是存在着线程并发数由少变多的过程,而锁升级也伴随着线程并发数增多,抢占锁更激烈而升级。

四、锁升级的代码实现

4.1 无锁->偏向锁

public class NoLockExample {static JOLObject jolObject=new JOLObject();public static void main(String[] args) {//打印对应的对象头信息System.out.println(ClassLayout.parseInstance(jolObject).toPrintable());}
}

输出结果:

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

结果分析:这里显示的是偏向锁,当创建的时候,会有其他的线程使用对象而使对象的锁结构从无锁转为偏向锁。

4.2、偏向锁转为轻量级锁

未完待续

五、不同锁结构应用场景

5.1、无锁结构

   无锁结构的应用在业务中比较常见,如果特定的业务场景没有多线程共享访问全局变量的话,就不需要使用锁结构,因为不涉及线程安全问题。

5.2、偏向锁结构

   根据偏向锁自身的特点,如果只有单个线程对共享变量进行访问的场景,可以将此全局变量设置为偏向锁对象,即该锁对象对特定的线程具有优先级。常见业务类型有在主线程中的全局变量没有其他变量访问的场景。

5.3、轻量级锁结构

   根据轻量级锁特点,常见于多个线程竞争锁资源的场景,轻量级锁不是公平锁,但是可以通过轻量级锁实现公平锁。常见业务场景:秒杀活动等。

5.4、重量级锁结构

  重量级锁由于阻塞其他线程的原因,所以在对全局变量的访问上具有最高的安全性,常见于金融系统等安全系数要求较高的场景。


http://chatgpt.dhexx.cn/article/ItxOIjm3.shtml

相关文章

简单理解重量级锁、轻量级锁、偏向锁

全文使用synchronized来说明。 synchronized给对象上锁&#xff0c;先上偏向锁&#xff0c;在上轻量级锁&#xff0c;最后上重量级锁。上什么锁&#xff0c;是jvm根据竞争程度自行变换的。 重量级锁 计算机操作系统本有Monitor对象&#xff0c;称为管程。在java里面看不到此对…

synchronized锁升级之偏向锁

目录 一、什么是偏向锁&#xff1f; 二、偏向锁原理 三、偏向锁演示 四、偏向锁的处理流程 五、偏向锁的撤销 六、偏向锁的好处 一、什么是偏向锁&#xff1f; HotSpot作者经过研究实践发现&#xff0c;在大多数情况下&#xff0c;锁不仅不存在多线程竞争&#xff0c;而…

Java无锁、偏向锁、轻量级锁、重量级锁,锁升级过程

2. 锁 2.1 无锁 Java对象刚创建时还没有任何线程来竞争&#xff0c;说明该对象处于无锁状态&#xff08;无线程竞争它&#xff09;&#xff0c;这时偏向锁标识位是0&#xff0c;锁状态是01 。 2.2 偏向锁 偏向锁是指一段同步代码一直被同一个线程所访问&#xff0c;那么该…

偏向锁的获取和撤销详解

Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗&#xff0c;引入了偏向锁和轻量级锁&#xff1b;在Java SE 1.6 中&#xff0c;锁共有4种状态&#xff0c;级别从底到高依次是&#xff1a;无锁状态、偏向锁状态、轻量级锁和重量级锁状态&#xff0c;这几种状态会随着竞争情况…

一篇文章说清 :无锁、偏向锁、轻量级锁、重量级锁

文章目录 前言一、无锁二、偏向锁三、轻量级锁&#xff08;自选锁&#xff09;四、重量级锁锁升级场景 前言 JDK1.6为了减少获得锁和释放锁所带来的性能消耗&#xff0c;引入了“偏向锁”和“轻量级锁”&#xff0c;所以在JDK1.6里锁一共有四种状态&#xff0c;无锁状态&#x…

synchronized的偏向锁、轻量级锁和重量级锁

文章目录 Java对象头偏向锁批量重偏向批量撤销 轻量级锁重量级锁Monitor 原理 Java对象头 普通对象 Mark Word在64 位虚拟机中的结构为 Mark Word后三&#xff08;两&#xff09;位表示 001&#xff1a;无锁状态101&#xff1a; 偏向锁00&#xff1a; 轻量级锁10&#xff1a…

Java synchronized偏向锁、轻量级锁、重量级锁

简介 synchronized锁共有偏向锁、轻量级锁、重量级锁三种类型&#xff0c;而这三种类型的加锁方式都是相同的&#xff0c;写代码时不用考虑加哪种锁。使用锁时对象首先会变为偏向锁状态&#xff0c;当有其它线程获取锁时会升级为轻量级锁&#xff08;没有竞争锁&#xff09;&am…

java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁

之前做过一个测试&#xff0c;详情见这篇文章《多线程 1操作的几种实现方式&#xff0c;及效率对比》&#xff0c;当时对这个测试结果很疑惑&#xff0c;反复执行过多次&#xff0c;发现结果是一样的: 1. 单线程下synchronized效率最高&#xff08;当时感觉它的效率应该是最差…

偏向锁、轻量级锁、重量级锁的区别和解析

为了换取性能&#xff0c;JVM在内置锁上做了非常多的优化&#xff0c;膨胀式的锁分配策略就是其一。理解偏向锁、轻量级锁、重量级锁的要解决的基本问题&#xff0c;几种锁的分配和膨胀过程&#xff0c;有助于编写并优化基于锁的并发程序。 内置锁的分配和膨胀过程较为复杂&…

偏向锁是什么

偏向锁操作流程偏向锁&#xff0c;顾名思义&#xff0c;它会偏向于第一个访问锁的线程&#xff0c;如果在接下来的运行过程中&#xff0c;该锁没有被其他的线程访问&#xff0c;则持有偏向锁的线程将永远不需要触发同步 但是从我们跑的代码输出却看不到偏向锁这个东东。为啥对…

偏向锁的基本原理

偏向锁的基本原理 前面说过&#xff0c;大部分情况下&#xff0c;锁不仅仅不存在多线程竞争&#xff0c;而是总是由同一个线程多次获得&#xff0c;为了让线程获取锁的代价更低就引入了偏向锁的概念。怎么理解偏向锁呢&#xff1f;当一个线程访问加了同步锁的代码块时&#xff…

偏向锁,轻量级锁,重量级锁的核心原理

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章是关于并发编程中偏向锁&#xff0c;轻量级锁&#xff0c;重量级锁的核心原理知识记录。 本篇文章记录的基础知识&#xff0c;适合在学Java的小白&#xff0c;也适…

深入探究synchronize锁机制

synchronized 有三种方式来加锁&#xff0c;分别是&#xff1a; **1. 修饰实例方法&#xff1a;**作用于当前实例加锁&#xff0c;进入同步代码前要获得当前实例的锁 **2. 静态方法&#xff1a;**作用于当前类加锁&#xff0c;进入同步代码前要获得当前类的锁 **3. 修饰代码块&…

偏向锁的原理与实战

文章目录 1. 偏向锁的核心原理2. 偏向锁代码演示3. 偏向锁的膨胀与撤销 1. 偏向锁的核心原理 如果不存在线程竞争的一个线程获得了锁&#xff0c;那么锁就进入偏向状态&#xff0c;此时Mark Word的结构变为偏向锁结构&#xff0c;锁对象的锁标志位&#xff08;lock&#xff09;…

Java并发 | 19.[锁机制] 偏向锁(CAS)

文章目录 1. 偏向锁分析回顾轻量级锁偏向锁的优势 2. 偏向锁&#xff08;CAS&#xff09;2.1. 偏向锁流程概述2.2. 锁升级 参考资料 1. 偏向锁分析 回顾轻量级锁 在上文 Java并发 | 18.[锁机制] 轻量级锁&#xff08;CAS自旋锁&#xff09;中对轻量级锁进行过解析&#xff0c…

Synchronized-偏向锁

偏向锁是什么&#xff1f; 是jdk1.6引入的一种锁优化方式。让 锁对象 偏心于第一次获取锁的线程&#xff0c;记住它的id&#xff0c;当下一次再有线程获取锁的时候&#xff0c;与记录的ID匹配&#xff0c;直接获取锁就行。是一种load-and-test的过程。 引入目的&#xff1f; …

面试题-- 什么是偏向锁

所谓的偏向&#xff0c;就是偏心&#xff0c;即锁会偏向于当前已经占有锁的线程 。 大部分情况是没有竞争的&#xff08;某个同步块大多数情况都不会出现多线程同时竞争锁&#xff09;&#xff0c;所以可以通过偏向来提高性能。即在无竞争时&#xff0c;之前获得锁的线程再次获…

Java中的偏向锁,轻量级锁, 重量级锁解析

文章目录 参考文章Java 中的锁一些先修知识synchronized 关键字之锁的升级&#xff08;偏向锁->轻量级锁->重量级锁&#xff09;无锁 -> 偏向锁偏向锁的撤销&#xff08;Revoke&#xff09;偏向锁的批量再偏向&#xff08;Bulk Rebias&#xff09;机制偏向锁 -> 轻…

条件分布

1.离散型&#xff1a; 例1&#xff1a; 2.连续型&#xff1a;