thread2.join(); } private static void printHeader() { synchronized (obj) { System.out.println 主线程进入,锁升级为 00(轻量级锁) OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 线程 1 和线程 2 同时进入 ,存在竞争 ,升级位重量级锁 10 OFFSET SIZE TYPE DESCRIPTION VALUE 线程 1 进入,无锁竞争,锁升级为轻量锁 000 OFFSET SIZE TYPE DESCRIPTION VALUE 0 线程 2 进入,存在锁竞争,锁升级为轻量锁 010 OFFSET SIZE TYPE DESCRIPTION VALUE 0
知道了 synchronized 的概念,回头来看标题,它说的锁升级到底是个啥?对于不太熟悉锁升级的人来说,可能会想: 所谓锁,不就是啪一下锁上就完事了吗?升级是个什么玩意?这跟打扑克牌也没关系啊。 对于熟悉的人来说,可能会想: 不就是「无锁 ==> 偏向锁 ==> 轻量级锁 ==> 重量级锁 」吗? 你可能在很多地方看到过上面描述的锁升级过程,也能直接背下来。 (1)无锁 无锁 这个可以理解为单线程很快乐的运行,没有其他的线程来和其竞争。 (2)偏向锁 偏向锁 首先,什么叫偏向锁? 锁升级 了解完 4 种锁状态之后,我们就可以整体的来看一下锁升级的过程了。 锁升级过程 EOF 其实偏向锁还有一个撤销的过程,也是有代价的,但相比于偏向锁带好的好处,是能够接受的。但我们这里重点的还是关注锁升级的具体逻辑和细节,关于锁升级的过程就聊到这里。
Synchronized锁原理和锁升级过程 Synchronized原理 1.Synchronized修饰范围 修饰静态方法 此时锁的是位于元空间的Class字节码文件,也叫Class模板 修饰实例方法 Synchronized锁升级过程 无锁状态、偏向锁、轻量级锁、重量级锁 ,这是锁膨胀的过程,不可逆,但只有偏向锁可以变回无锁态。 解释名词 (1)无锁状态:对象还没有被任何线程锁定 (2)偏向锁: 经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁 。 让有其他线程来竞争时就会要么自己已经执行完毕锁偏向给了其他线程,要么竞争失败,锁会在一个安全点上撤销,并升级委轻量级锁。 锁的详细升级过程 1.一开始对象是无锁状态的 2.一个线程尝试执行Synchronize代码块时,成功获得对象的锁,通过CAS操作往该对象markword中插入当前线程id, 同时修改偏向锁的标志位 。
前言在 Java 并发编程领域,synchronized关键字堪称保障线程安全的中流砥柱。随着 JDK 版本的迭代演进,synchronized锁的性能优化也在持续推进,其中锁升级机制尤为关键。 在撤销过程中,JVM 会遍历持有该对象的线程栈,检查是否存在该对象的锁记录。若存在,则将偏向锁升级为轻量级锁。 五、锁升级过程总结在 Java 中,synchronized锁的升级过程是一个逐步优化的过程。当一个线程首次访问同步块时,JVM 会尝试使用偏向锁提升性能。 在实际开发中,我们应尽量避免不必要的锁竞争,合理运用synchronized关键字,充分发挥锁升级机制的优势。六、JDK 版本与锁升级锁升级机制在不同的 JDK 版本中存在差异。 七、总结本文深度剖析了 Java 中synchronized锁的升级过程,涵盖偏向锁、轻量级锁和重量级锁的含义、业务场景、实现方式、模拟示例以及底层原理。
synchronized锁升级 博主 默语带您 Go to New World. 对象头结构 在 32 位虚拟机中,MarkWord 包括: 哈希码(25 位) 分代年龄(4 位) 锁标志(2 位) 偏向标志(1 位) 无锁状态 初始状态,未被任何线程持有。 标志位: 偏向锁标志:0 锁标志:01 偏向锁 特点:偏向锁旨在优化单线程情况下的锁获取开销,避免CAS操作。 获取过程: 检查锁标志为 01 且偏向锁标志为 0。 偏向锁的升级 如果锁竞争发生,偏向锁可能升级为轻量级锁。 轻量级锁 特点:通过自旋等待来实现,适合短时间内无大量竞争的场景。 撤销:轻量级锁会在使用后主动撤销,将 MarkWord 恢复原值。 升级:若自旋超出限定次数或有大量线程竞争,升级为重量级锁。
public void test2(){ synchronized (this){ } } } 我们用javap来分析一下编译后的class文件,看看synchronized 反之,如果某个锁很少有自旋成功,那么以后的自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。 安全点日志默认输出到stdout,一是stdout日志的整洁性,二是stdout所重定向的文件如果不在/dev/shm,可能被锁。 2. 缺点:同自旋锁相似:如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁,那么维持轻量级锁的过程就成了浪费。 七、重量级锁 轻量级锁膨胀之后,就升级为重量级锁了。 ,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。
64操作系统下,Mark Word的长度是64,在加Klass Word(32位),一共是96位,其实对象头长什么样其实不是本文的重点,本文的重点是验证锁升级的过程,所以我们只需要关注对象头中Mark 锁升级的过程 锁状态 25bit 4bit 1bit 2bit 23bit 2bit 是否偏向锁 锁标志位 1 无锁 对象的HashCode 分代年龄 0 01 2 无锁 对象的HashCode 上面表格中2->3的过程。 状态为00,轻量级锁状态 线程2释放锁后,状态为001,此时为不可偏向的无锁状态。 _哔哩哔哩_bilibili synchronized锁原理分析(一、从Java对象头看synchronized锁的状态)_liujianyangbj的博客-CSDN博客 Java的对象头和对象组成详解
synchronized锁:由对象头中的Mark Word根据锁标志位的不同而被复用及锁升级策略 2、Synchronized的性能变化 java5以前,只有Synchronized,这个是操作系统级别的重量级操作 3、java6开始,优化Synchronized Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁 需要有个逐步升级的过程,别一开始就捅到重量级锁 3、Synchronized 锁种类及升级步骤 1、多线程访问情况,3种 1)只有一个线程来访问,有且唯一Only One 2)有2个线程A、B来交替访问 3)竞争激烈,多个线程来访问 2、升级流程 synchronized用的锁是存在 锁升级过程总结:一句话,就是先自旋,不行再阻塞。 JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁。
如果该线程正在执行同步代码块时有其他线程在竞争(指其他线程尝试 CAS 让 Mark Word 设置自己的线程 ID),会被升级为轻量级锁。 如果成功,表示线程拿到了锁。如果失败,则进行自旋(自旋锁),自旋超过一定次数时升级为重量级锁,这时该线程会被内核挂起。 升级为重量级锁时会在堆中创建 monitor 对象,并将 Mark Word 指向该 monitor 对象。 图片来自:死磕Synchronized底层实现--重量级锁 锁升级的流程图 ? 图片来自:Java Synchronised机制 0x05:锁降级 Hotspot 在 1.8 开始有了锁降级。 死磕Synchronized底层实现--概论 中: 因为在Java中任意对象都可以用作锁,因此必定要有一个映射关系,存储该对象以及其对应的锁信息(比如当前哪个线程持有锁,哪些线程在等待)。
,biased_lock:偏向锁,1位lock:锁状态,2位biased_lock + lock: 最后3位控制对象的5种状态对象状态:无锁、偏向锁、轻量锁、重量锁、gc标记,只有锁可以升级但不能降级, 意味着偏向锁升级成轻量级锁后不能降级成偏向锁。 synchronized锁的升级过程(锁/对象状态)通过上述对象头介绍,应该清楚了,synchronized加锁主要改变的是对象头的信息,改变的是64位对象头,最后的三位。 代码演示synchronized锁的升级过程synchronized加锁,一把锁,在没有竞争的情况下,被同一个对象多次获取,所以没必要一直加锁操作,以此来减少CPU资源,所以就会导致加了锁,最后三位数还是 jdk优化之后synchronized加锁,不会立即升级成重量级锁,只有在多个线程并发,加锁,对象状态才会升级重量级锁。
JDK1.6 之后 默认开启如下锁无锁状态偏向锁轻量级锁重量级锁图片随着锁的竞争,锁可以从 偏向锁 升级到 轻量级锁,再升级到 重量级锁但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级Java ,2 位锁标志 (01) 组成偏向状态由 23 位线程标识,2 位时间戳,4 位 GC 分代年龄,1 位偏向锁标识,2 位锁标识 (01) 组成轻量级锁状态由 30 位指针 (指向锁记录) 2 位锁标识 (1)通过 CAS 操作尝试把线程中复制的 Displaced Mark Word 对象替换当前的 Mark Word(2)如果替换成功,整个同步过程就完成了(3)如果替换失败,说明有其他线程尝试过获取该锁 (此时锁已膨胀)那就要在释放锁的同时,唤醒被挂起的线程适应性自旋自旋锁从轻量级锁获取的流程中我们知道,当线程在获取轻量级锁的过程中执行 CAS 操作失败时,是要通过自旋来获取重量级锁的问题在于,自旋是需要消耗 的优化但是这两种锁也不是完全没缺点的,比如竞争比较激烈的时候,不但无法提升效率,反而会降低效率,因为多了一个锁升级的过程这个时候就需要通过 -XX:-UseBiasedLocking 来禁用偏向锁几种锁的对比图片锁升级代码演示创建一个
三、锁升级全流程核心原理 synchronized的锁升级是JVM为了减少同步开销而做的自适应优化,核心逻辑是:根据竞争激烈程度,从低开销锁逐步升级到高开销锁,锁的膨胀过程在持有期间是单向的,不可降级, CAS成功:锁释放完成,对象回到无锁状态。 CAS失败:说明自旋过程中锁已被升级为重量级锁,需进入重量级锁的释放流程,唤醒等待队列中的线程。 准确结论是:锁的膨胀过程在持有期间是单向的,只能从低级别升级到高级别,不可降级;但当锁完全释放后,对象的Mark Word会被重置为无锁状态,下一次竞争会重新开始锁升级流程。 七、高频面试题与标准答案 简述synchronized的锁升级全流程答:synchronized的锁升级是JVM为减少同步开销做的自适应优化,基于对象的Mark Word实现,流程如下: 无锁状态:对象刚创建 锁的膨胀过程在持有期间是单向的,不可降级,锁完全释放后会重置为无锁状态。
然而,尽管synchronized在日常使用中简单方便,但它的内部实现及其升级过程却蕴藏着极大的复杂性和精妙之处。 在这本期文章中,以JDK17u为例,我将详细记录对synchronized底层机制及其锁升级过程的探究和学习过程。 重量级锁会导致竞争锁的线程进入BLOCKED状态,只有等待获取锁的线程释放锁后,其他线程才能获取到锁 synchronized的这四种锁状态是在JVM层面自动进行的,深入理解synchronized锁升级过程可以帮助开发者更好地对程序进行性能调优或设计更好的并发控制策略 然后当线程2试图获取锁时,偏向锁被撤销,变回无锁状态。再次由线程1获取时,锁被升级为轻量级锁。最后,当线程2再次试图获取锁时,锁被升级为重量级锁。 } } 这一块时序图所基于的理论是“当尝试获取锁失败时,synchronized才会发生升级”,synchronized的锁升级过程是一种优化策略,其目的是在不同的竞争条件下尽可能减少系统的开销
synchronized实现原理及锁升级过程 QQ截图20190730134117.png 前言: synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差,但是随着版本的升级,synchronized已经变得原来越强大了,本文带大家了解的是synchronized实现原理及锁升级过程,希望可以帮助到大家。 2.实现原理 2.1 Java对象头 synchronized用的锁存在Java对象头里,Java对象头里的Mark Word默认存储对象的HashCode、分代年龄和锁标记位。 为了提高获得锁和释放锁的效率,锁可以升级但不能降级,意味着偏向锁升级为轻量级锁后不能降级为偏向锁。 2.轻量级锁 线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的MarkWord复制到锁记录中,即Displaced Mark Word。
synchronized 是 Java 中实现线程同步的核心机制,其锁状态会随着竞争激烈程度动态升级,以提高并发性能。从 无锁 到 重量级锁 的升级过程包括四个阶段,且 不可逆。 无锁状态特点: 对象初始化时处于无锁状态,此时没有线程持有锁。Mark Word(对象头的一部分)中存储对象的哈希码、分代年龄等信息,锁标志位为 01 适用场景: 单线程环境或未发生竞争的情况。2. 监控锁状态: 使用工具(如 JOL)查看对象头信息,分析锁升级过程替代方案: 在高并发场景下,可结合 ReentrantLock 或 StampedLock 提升灵活性通过理解锁升级机制,开发者可以更好地优化多线程程序的性能 4s),确保偏向锁生效 Thread.sleep(5000); // 首次获取锁,触发偏向锁 synchronized (obj) { .toPrintable()); } // 创建多个线程竞争锁,触发轻量级锁 for (int i = 0; i < 2; i++) {
} 锁升级 首先过一下synchronized锁升级的过程 1.偏向锁 当只有一个线程获得了锁,锁就进入偏向模式,MarkWord标识偏向状态,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程 2.轻量级锁 当有其它线程要获取锁,竞争不是很激烈,锁进入轻量级锁,MarkWord标识轻量级状态,此时等待锁的线程开始自旋,即空循环等待锁释放,此过程不释放cpu。 原始的synchronized是直接使用重量级锁,才会导致性能很低,加入锁升级才使得synchronized性能获得很大提升。 理解 以上讲解了synchronized锁升级的过程,如果不好理解,还是拿现实生活举个例子: 假设某公司有多个会议室,每个团队需要获取到会议室的锁才能进去开会,会议室门口挂着一个写字板,时刻记录当前会议室使用状态 这就是轻量级锁,偏向锁出现了竞争会升级为轻量级锁,因为大部分线程占用锁的时间不会特别长,所以等待线程刚开始不需要挂起,只需要通过空转自旋等待,一般很快就会获取到锁,比过程一直占用着cpu。
锁升级 由于synchronized性能问题在JDK1.6前饱受诟病,同时和@author Doug Lea大神写的目前在JUC下的AQS实现的锁差距太大,synchronized开发人员感觉脸上挂不住 升级过程 ? 结果可以看出:所有线程结束后已经不存在竞争时并不会变为无锁状态,也就是说锁只能升级,不能降级,竞争比较严重时升级为重量级锁,偏向锁和轻量级锁在用户态维护不需要经过内核态,重量级锁需要切换到内核态(os) 轻量级锁 在锁升级过程中有一个轻量级锁,轻量级锁一般指的就是自旋锁CAS(Compare And Exchange),对java开发者来说这种锁也可以看成无锁,因为在java代码层面没有锁的代码。 最后留下几个问题思考 简述锁升级过程? 自旋锁什么时候升级为重量级锁? 为什么有自旋锁还需要重量级锁? 偏向锁是否一定比自旋锁效率高?
在 32 位的虚拟机中: 在 64 位的虚拟机中: 上图中的偏向锁和轻量级锁都是在 java6以后对锁机制进行优化时引进的,下文的锁升级部分会具体讲解,synchronized 关键字对应的是重量级锁, 锁的优化 锁的 4 中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高),整个锁的状态从低到高变化的过程被称为所升级。 为什么要引入偏向锁? 轻量级锁什么时候升级为重量级锁? 重量级锁把除了拥有锁的线程都阻塞,防止 CPU 空转。」 ❝ 注意:为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。 一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。
synchronized是jvm内部的一把隐式锁,一切的加锁和解锁过程是由jvm虚拟机来控制的,不需要我们认为的干预,我们大致从了解锁,到synchronized的使用,到锁的膨胀升级过程三个角度来说一下 我们还可以将synchronized锁放置在方法上。 说到这我们就可以来看一下我们锁的膨胀升级过程了。 锁的膨胀升级 我们说过了对象头的内容,接下来可以说说我们的锁内部是如何升级上锁的了。 从无锁到重量级锁的一个升级过程,我们来边画图,边详细看一下。 无锁状态: ? ,优先看最后2bit位,是01,所以说我们的对象可能无锁或者偏向锁状态的,继续前移一个位置,有1bit专门记录是否为偏向锁的,1代表是偏向锁,0代表无锁,刚刚开始的时候一定是一个无锁的状态,这个不需要多做解释
在 32 位的虚拟机中: 在 64 位的虚拟机中: 上图中的偏向锁和轻量级锁都是在 java6以后对锁机制进行优化时引进的,下文的锁升级部分会具体讲解,synchronized 关键字对应的是重量级锁 锁的优化 锁的 4 中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高),整个锁的状态从低到高变化的过程被称为所升级。 为什么要引入偏向锁? 轻量级锁什么时候升级为重量级锁? 重量级锁把除了拥有锁的线程都阻塞,防止 CPU 空转。」 ❝ 注意:为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。 一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。