纸上得来终觉浅->代码详解锁升级

锁升级过程(无锁、偏向锁、轻量级锁、重量级锁) synchronized锁升级:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 阅读详情

        背景

        最近再看关于锁升级的内容,一方面这个是编写代码时程序性能提高的一个利器,另一方面这部分也会是面试时候的热门话题。那么作者最开始也是通过b站视频包括一些csdn上面的资料去看,最终发现只是有一些结论,而没有具体的例子。这篇文章里一切都是以jdk8为环境基础展开。

        一种观点是:锁在没有竞争的情况下会是偏向锁,在遇到竞争的时候会升级成轻量级锁,再遇到竞争了这个锁会进行一定次数的自旋,当次数计满了就会升级成为重量级锁。

        另一种观点是:无论是轻量级锁还是偏向锁在遇到竞争的时候都会升级成重量级锁,而重量级锁会通过自旋的操作来避免阻塞的发生来提高性能,并且自旋也是自适应的。

        值得一提的是,在jdk8中,偏向锁的开启是有延迟的,并不是一开启的时候就是偏向锁,而自旋锁是一开始就有的。

        下面我们就围绕着代码和实际情况来看偏向锁,轻量级锁,重量级锁,以及偏向锁的重偏向和撤销的过程。

        代码

        这里先把测试的代码给大家,根据满叔的资料进行改编。大家在观察代码的时候要注意两个线程是否发生了竞争,包括这个日志打印的顺序是怎么样的。

package com.zzm.juc.syn;

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;

import java.util.Vector;
import java.util.concurrent.locks.LockSupport;

/**
 * @BelongsProject: happystudy
 * @BelongsPackage: com.zzm.juc.syn
 * @Author: zzm
 * @CreateTime: 2024-03-27  11:23
 * @Description: TODO
 * @Version: 1.0
 */

@Slf4j(topic = "c.SyncWaitNotify")
public class SyncWaitNotify {
    private int flag;
    private int loopNumber;
    static Thread t1,t2,t3;

    private final Object lock = new Object();

    public SyncWaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

    public void print(int waitFlag, int nextFlag, String str) {
        log.debug("对象头" + ClassLayout.parseInstance(lock).toPrintable());
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                while (this.flag != waitFlag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                flag = nextFlag;
                log.debug("对象头" + ClassLayout.parseInstance(lock).toPrintable());
                this.notifyAll();
            }
        }
    }

    private static void test4() throws InterruptedException {
        Vector<Dog> list = new Vector<>();
        int loopNumber = 39;
        t1 = new Thread(() -> {
            for (int i = 0; i < loopNumber; i++) {
                Dog d = new Dog();
                list.add(d);
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
            }
            LockSupport.unpark(t2);
        }, "t1");
        t1.start();
        t2 = new Thread(() -> {
            LockSupport.park();
            log.debug("===============> ");
            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
            LockSupport.unpark(t3);
        }, "t2");
        t2.start();
        t3 = new Thread(() -> {
            LockSupport.park();
            log.debug("===============> ");
            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
        }, "t3");
        t3.start();
        t3.join();
        Dog dd = new Dog();
        log.debug(ClassLayout.parseInstance(dd).toPrintable());
        for(int i=0;i<2;i++){
            synchronized (dd) {
                log.debug("不可偏向"+ClassLayout.parseInstance(dd).toPrintable());
            }
        }
        log.debug(ClassLayout.parseInstance(dd).toPrintable());

    }

    private static void test3() throws InterruptedException {
        Vector<Dog> list = new Vector<>();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                Dog d = new Dog();
                list.add(d);
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
            }
            synchronized (list) {
                list.notify();
            }
        }, "t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            synchronized (list) {
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("===============> ");
            for (int i = 0; i < 30; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
        }, "t2");
        t2.start();
    }

    private static void test2() throws InterruptedException {
        Dog d = new Dog();
        Thread t1 = new Thread(() -> {
            synchronized (d) {
                log.debug(ClassLayout.parseInstance(d).toPrintable());
            }
            synchronized (TestBiased.class) {
                TestBiased.class.notify();
            }
        }, "t1");
        t1.start();
        Thread t2 = new Thread(() -> {
            synchronized (TestBiased.class) {
                try {
                    TestBiased.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug(ClassLayout.parseInstance(d).toPrintable());
            synchronized (d) {
                log.debug(ClassLayout.parseInstance(d).toPrintable());
            }
            log.debug(ClassLayout.parseInstance(d).toPrintable());
        }, "t2");
        t2.start();
    }
    public static void main(String[] args) throws InterruptedException {
//        SyncWaitNotify syncWaitNotify = new SyncWaitNotify(1, 2);
//        new Thread(() -> {
//            syncWaitNotify.print(1, 2, "a");
//        }).start();
//        new Thread(() -> {
//            syncWaitNotify.print(2, 3, "b");
//        }).start();
//        new Thread(() -> {
//            syncWaitNotify.print(3, 1, "c");
//        }).start();
        test4();
//        test3();
//        test2();
    }

}

markword阅读:

这里的toPrintable读出来的字符串,前两行是markword,并且要倒着读也就是从后往前读,这个详细了解可以去搜索一下这个classlayout的相关博客是如何查看对象头信息的。

锁升级

比如说这段代码

t1,t2没有竞争,打印出来头信息最后两位为0

那么全程这个就是轻量级锁,因为偏向锁是有延迟的,所以我们加一个参数

 

这个对象加锁后就会变成偏向锁

 

带偏向锁的情况下 发生了竞争

 

可以看到一开始是偏向锁 

 

 最后变成了10,直接成为了重量级锁跳过了轻量级。而经过测试轻量级锁发生竞争也会立刻变成重量级锁。

  test3->重偏向

          一个对象,关闭偏向锁延迟,上来他是偏向锁,这个时候两个线程不会竞争但是会交替运行,那么他会由偏向锁,变成轻量级锁,现在准备30个对象,把他放到list里面,还是两个线程没有竞争的情况,t1线程对list里面对象加锁,都会变成偏向锁,在加锁的时候。

        这个时候t1执行完毕到t2了,t2又把这list里面的30个对象依次取出来,进行加锁,偏向锁的重偏向是有阈值的,这里为20。那么在t2线程里面拿出的前20个对象,他的轻量级锁的线程id都是t1,他们都会从偏向锁变成轻量级锁。

但是超过20次了,

 

 

第21个jvm可能就会认为这个对象对于t1线程是不是偏向错了,因此他把后面还没来得及遍历的10个对象,一起把偏向锁的线程id变成了t2线程。

test4->批量撤销

        但是同理,假设这个时候对象变成了40个,t1对每个对象加锁完之后,t2右对他们加锁,t2加锁完之后,t3又对他们进行加锁的重偏向,

当阈值达到40的时候,jvm可能就会认为这个类下的对象竞争比较激烈就不允许给这个类下的对象设置偏向锁了,再创建一个,加锁的时候也就变成了轻量级锁。

synchronized锁升级过程详解 注:synchronized锁流程如下 第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁” 第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空。 第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建.. 阅读详情

相关推荐

Java 锁升级的过程详解

Java锁升级过程包括从无锁状态到偏向锁、轻量级锁最终升级为重量级锁。偏向锁适用于单线程重复访问场景,通过标记线程ID减少同步开销;当出现竞争时升级为轻量级锁,使用CAS和自旋避免阻塞;若竞争激烈则升级为重量级锁,依赖操作系统互斥量实现,涉及线程阻塞和唤醒。锁升级是单向不可逆的,JVM会根据并发程度自动选择最优锁机制,其中偏向锁和轻量级锁是性能优化的关键。理解该机制有助于编写高效并发程序。

嘻嘻哈哈学技术 625

日志组件slf4j介绍及配置详解

基本介绍每一个Java程序员都知道日志对于任何一个Java应用程序,尤其是服务端程序是至关重要的,而很多程序员也已经熟悉各种不同的日志库如java.util.logging、Apache log4j、logback。但如果你还不知道SLF4J(Simple logging facade for Java)的话,那么是时候去在你项目中学习使用SLF4J了。SLF4J不同于其他日志类库,与其它日志类库有

楚兴 15万+

深入解析进化算法中的选择与多样性

在计算科学的世界里,最优雅的解决方案往往源于最古老的智慧——大自然。进化算法(Evolutionary Algorithms, EAs)正是这样一种受达尔文自然选择学说启发的强大工具。它们通过模拟“适者生存”的过程,在庞大的可能性海洋中搜寻最优解。。:指的是深入挖掘和优化已知的优质解。这就像在一片已知的沃土上精耕细作,力求获得最高产量。强大的选择压力会促进这一过程。:指的是在整个搜索空间中寻找全新的、可能更优的解。这好比派遣探险队去发现新大陆,充满了未知与机遇。保持种群的多样性是探索的关键。

weixin_62533513的博客 713

Spring Boot之更简洁使用日志

文章目录1 原始日志用法缺点:2 优化版2.1 引入pom包:2.2 使用2.3 对比 1 原始日志用法 springboot项目中使用@Slf4j,一般如下: private static final Logger log = LoggerFactory.getLogger(xxx.class); log.info(""); 缺点: 每个类中需要根据类名去实例化,比较繁琐。 2 优化版 引入...

二营长 431

直线电机驱动技术原理与应用解析

直线电机作为直接电能-直线运动转换装置,其核心原理基于电磁感应定律和洛伦兹力公式。通过三相交流电在定子绕组中产生行波磁场,与动子永磁体相互作用实现精准推力控制。相比传统旋转电机传动系统,直线电机具有零传动间隙、高动态响应(加速度可达10g)和微米级定位精度等技术优势,这使其成为半导体光刻机、精密数控机床等高端装备的关键驱动部件。现代直线电机驱动单元通常采用三环控制架构(电流环、速度环、位置环),配合SiC功率器件和高分辨率光栅反馈,实现纳米级运动控制。随着模块化设计和直接冷却技术的发展,直线电机正朝着更高功

weixin_26802933的博客 259

脑电分析:为什么大脑信号会“变同步”?——事件相关同步化与去同步化 (ERS/ERD) 全景解析

脑电分析中的事件相关同步化(ERS)和去同步化(ERD)是研究大脑动态节律的重要方法,反映特定任务下脑电频段能量的变化。ERD表示神经元活动分散(功率下降),提示脑区活跃处理信息;ERS则反映神经元同步(功率增强),常与静息状态相关。本文详解了ERS/ERD的生理机制、计算流程(包括基线校正和功率谱分析),并提供了Python实现示例,展示如何通过Welch法和STFT进行时频分析,生成ERD/ERS热力图。该技术广泛应用于运动想象脑机接口、癫痫检测等领域,为理解大脑功能状态提供了量化工具。

qq_32516809的博客 1394

java并发编程——同步模式之顺序控制

同步模式之顺序控制(面试经常考) 比如,必须先 2 后 1 打印 1.1 wait notify 版 package cn.itcast.test; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "c.Test25") public class Test25 { static final Object lock = new Object(); // 表示 t2 是否运行过 static boolean t2runned = fa

mirana的专栏 336

YOLO+OpenClaw缺陷检测智能体 | 低代码工业质检自动化方案 | 含YOLO模型训练/智能体集成/任务全流程,支持离线日报/预警/趋势分析,解决实时性瓶颈问题(下篇)

某PCB板制造商需要对生产线上的PCB板进行缺陷检测,主要检测划痕、凹坑、污渍和裂纹等缺陷。原有人工检测方式效率仅为200件/小时,漏检率达5%,且检测人员长期工作易疲劳。

qq_36130719的博客 205

synchronized锁升级过程

从理论到实操,详细介绍了锁的底层升级过程并用代码进行验证,加深记忆

在路上 1万+

OpenCV实战(24)——相机姿态估计

如果物体的 3D 结构是已知的,那么就可以预测物体如何投影到相机的传感器上,图像形成的过程由投影方程描述。当方程的大部分项已知时,就可以通过观察一些图像来推断其他元素 (2D 或 3D) 的值。相机姿态估计就是通过几个已知坐标的特征点,以及这些点在照片中的成像位置,求解出相机位于坐标系内的坐标与旋转角度。在本节中,我们将研究观察已知 3D 结构时的相机姿态估计问题。

盼小辉丶的博客 2万+

Spring AI 实战:第七章、Spring AI Advisor机制之记忆大师

从金鱼到最强大脑通过 Spring AI 的记忆功能,我们成功让 AI:记住了用户身份实现了多用户隔离做到了持久化存储最终效果用户:“和上次一样” → AI 秒懂用户:“你知道我的名字吗?” → AI:“当然,张三!AI的进化路线🐟金鱼脑 →🧠最强大脑 →🏛️记忆宫殿管理员。

liaokailin的专栏 1516

锁的四种状态及升级过程

锁的四种状态与锁升级过程 图文详解 一、前言 锁的状态总共有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁,这四种锁状态分别代表什么,为什么会有锁升级?其实在 JDK 1.6之前,synchronized 还是一个重量级锁,是一个效率比较低下的锁,但是在JDK 1.6后,Jvm为了提高锁的获取与释放效率对(synchronized)进行了优化,引入了偏向锁 和 轻量级锁,从此以后锁的状态就有了四种(无锁、偏向锁、轻量级锁、重量级锁),并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的...

徐赤赤的博客 1万+

基于PPT的三维光路结构示意图绘制实例演示-技术细节

基于PPT的三维光路结构示意图绘制实例演示-技术细节

简单光学 7461

java锁升级过程

java中对象锁有4种状态:(级别从低到高) 1.无锁状态 2.偏向锁状态 3.轻量级锁状态 4.重量级锁状态 对象头分两部分信息,第一部分用于存储哈希码、GC分代年龄等,这部分数据被称为"Mark Word"。在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32bit空间中的25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固...

天高任鸟飞,海阔凭鱼跃 1万+

RK3566-预装APP

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档。

qq_24093081的博客 701

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

synchronized 锁升级详解

锁是一种用于控制对共享资源访问的机制,它可以确保在某个时刻只有一个线程或进程可以访问被锁定的资源,从而保证数据的一致性和安全性。在计算机科学中,锁常用于并发编程,以解决多线程或多个进程并发访问共享资源时可能出现的数据竞争和不一致性问题。锁升级是指将多个细粒度锁转换为粗粒度锁的过程,叫做锁升级,需要注意的锁只会升级不会降级,锁一旦升级到更高级别,就不会再回到低级别。无锁–>偏向锁–>轻量级锁–>重量级锁。如有错误的地方欢迎指出纠正。

weixin_42118323的博客 1797

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Java中Synchronized锁升级机制详解

当第一个线程尝试获取`synchronized`锁时,对象头将被标记为偏向锁状态。这意味着只有当前线程可以访问该对象,而不需要额外的同步开销。

小电玩 2978

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

锁的升级过程

Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。 ——引用自《Java并...

sspudding 1万+

Java中的锁升级

关于锁升级的四种级别,即如何升级

热爱编程的菜鸟 9820

synchronized 锁升级机制详解

Java中的锁会经历一个从无锁到偏向锁,再到轻量级锁,最后到重量级锁的升级过程,这种优化称为或。

huing的博客 1405

Java中锁的升级与死锁详解

锁类型锁的状态适用场景特点偏向锁初始锁状态没有其他线程竞争时几乎没有开销轻量级锁偏向锁竞争失败时存在轻微的锁竞争时使用 CAS 尝试获取锁重量级锁轻量级锁竞争失败时存在大量线程竞争时会导致线程阻塞与上下文切换死锁是指两个或多个线程因争夺资源而互相等待,导致程序无法继续执行。互斥条件:至少有一个资源必须处于非共享模式,即只能由一个线程使用。请求与保持条件:线程在请求资源时,已经持有其他资源。不剥夺条件:已经分配给线程的资源,在没有使用完之前,不能强行剥夺。循环等待条件。

一碗黄焖鸡三碗米饭的博客 1331

synchronized锁升级

synchronized锁升级

weixin_45794641的博客 1万+

锁的四种状态与锁升级过程 图文详解

无锁、偏向锁、轻量级锁、重量级锁,这四种锁状态分别代表什么,为什么会有锁升级?其实在 JDK 1.6之前,synchronized 还是一个重量级锁,是一个效率比较低下的锁,但是在JDK 1.6后,Jvm为了提高锁的获取与释放效率对()进行了优化,引入了偏向锁 和 轻量级锁,从此以后锁的状态就有了四种(无锁、偏向锁、轻量级锁、重量级锁),并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,也就是说只能进行锁升级(从低级别到高级别),不能锁降级(高级别到低级别)

m0_66328098的博客 4090

synchronized锁的升级流程

从理论到实操,详细介绍了锁的种类和底层升级流程,并通过代码进行验证,加深记忆。

汐泠的博客 3113

Synchronized锁的升级流程详解

在Java多线程编程中,synchronized关键字用于确保在同一时刻只有一个线程可以访问被锁定的资源,从而维护数据的一致性和安全性。然而,在多线程环境中,锁的频繁获取和释放会带来性能开销。为了提高性能,Java虚拟机(JVM)在JDK 1.6及以后的版本中引入了锁的升级机制,通过动态调整锁的策略来减少同步操作的开销。本文将详细解释synchronized锁的升级流程,包括无锁状态、偏向锁、轻量级锁和重量级锁四种状态及其转换过程。

qq_50801874的博客 1967
上一篇: ip地址开发场景问题
happystudy_neu
博客等级 码龄4年 1140粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值