首页 > 其他分享 >synchronized轻量级锁原理和实战(四)

synchronized轻量级锁原理和实战(四)

时间:2024-08-18 22:51:40浏览次数:13  
标签:实战 Word synchronized Mark 线程 轻量级 自旋 锁线

引入轻量级锁的目的

多线程竞争不激烈的情况下,通过CAS机制竞争减少重量级锁产生的性能消耗.重量级锁使用了操作系统底层的互斥锁,会导致线程在用户态和核心态之间切换.带来性能上的损耗.

轻量级锁的核心原理

轻量级锁存在的目的本身就是为了减少线程从内核态和用户态的切换,从而提高性能.通过经验可以发现好多加锁操作并不会持续很长时间.比如整数的自加操作,在很短的时间阻塞和唤醒线程显然不是很合理.轻量级锁是一种自旋锁,因为JVM本身就是一个应用,希望从应用层面通过自旋解决线程同步问题.

轻量级锁执行过程

抢锁线程进入临界区之前.如果内置锁(临界区的同步对象)没有锁定.JVM首先在抢锁线程的栈帧中建立一个锁记录(Lock Record),用于存储对象目前的Mark Word拷贝.

抢锁线程先处理好栈帧中的轻量级锁记录,然后就是最核心的CAS自旋.抢锁线程通过自旋操作,尝试将内置锁对象头的Mark Word的ptr_lock_record(锁记录指针)更新为抢锁线程中锁记录的地址.如果更新成功了,这个线程就拥有了这个对象锁.然后JVM会把Mark Word的锁记录标记位改为00(轻量级锁标志).

Mard Word的值被更新成功后,包含锁对象信息(如哈希表等)旧值会返回来,抢锁线程会找到一个地方将旧的Mark Word值存起来,所以线程在通过CAS自旋操作更新完Mark Word之后还会有两个善后操作.

1:将含有锁对象信息(如哈希表等)旧的Mark Word值保存在抢锁线程Lock Record的Displaced Mark Word(可以理解为放错的Mark Word)字段中,这一步起到备份作用,以便锁释放以后将旧的值恢复到锁对象头部.

2:抢锁线程将栈帧中的锁记录owner指针指向锁对象.

锁记录是线程私有的,每个线程都有自己的一份锁记录,在创建完锁记录后,会将内置锁对象的Mark Word拷贝到锁记录的Displaced Mark Word字段.这是因为什么?因为内置锁对象的Mark Word的结构会有所变化,Mark Word将会出现一个指向锁记录的指针,而不再存着无锁状态下的锁对象的哈希码等信息,所以必须将这些信息先暂存起来,供后面使用.

轻量级锁演示

public class LightweightLock {

    static final int MAX_TURN = 1000;

    public static void main(String[] args) throws InterruptedException {
        System.out.println(VM.current().details());
        //JVM偏向延迟锁.
       Thread.sleep(5000);
        ObjectLock objectLock = new ObjectLock();
        System.out.println("抢占锁前lock的状态:");
        objectLock.printObjectStruct();

        LockSupport.parkNanos(5000);
        CountDownLatch latch = new CountDownLatch(2);
        Runnable runnable = () -> {
            for (int i = 0; i < MAX_TURN; i++) {
                synchronized (objectLock) {
                    objectLock.increase();
                    if (i == 1) {
                        System.out.println("第一个线程占有锁,lock的状态为: ");
                        objectLock.printObjectStruct();
                    }
                }
            }
            latch.countDown();

            //线程虽然释放锁,但是存在死循环.
            for (int j = 0; ; j++) {
                //每一次循环等待一毫秒.
                   LockSupport.parkNanos(1000);
            }
        };
        new Thread(runnable).start();
        LockSupport.parkNanos(1000);

        Runnable LightweightRunnable = () -> {
            for (int i = 0; i < MAX_TURN; i++) {
                synchronized (objectLock){
                    if (i == MAX_TURN / 2) {
                        System.out.println("第二个线程占有锁,lock的状态为: ");
                        objectLock.printObjectStruct();
                    }
                        LockSupport.parkNanos(1000);
                }
            }
            //循环完毕.
            latch.countDown();
        };
        new Thread(LightweightRunnable).start();
        latch.await();
        LockSupport.parkNanos(2000);
        System.out.println("释放锁后的状态: ");
        objectLock.printObjectStruct();
    }
}

程序启动五秒以后,ObjectLocal状态为偏向锁.

 

关于偏向锁的执行结果和上一篇文章相同,就不啰嗦啦.

当第二个抢锁线程抢锁成功后,ObjectLock状态变成了轻量级锁.

 

可以看到lock标记位改为了00(轻量级锁标志),其中ptr_to_lock_record(锁记录指针)更新为抢锁线程栈帧中Lock Record的地址.

轻量级锁被释放后.ObjectLock状态变成无锁状态,lock标记位01(无锁标志).

轻量级锁的分类

轻量级锁主要有两种.普通自旋锁和自适应自旋锁.

1:普通自旋锁

普通自旋锁就是当线程来竞争锁时,抢锁线程会原地等待,而不是被阻塞.直到那个占有锁的线程释放锁.这个线程才可以获取锁.默认情况下,自旋的次数为10次.可以通过-XX:PreBlockSpin进行更改.

2:自适应自旋锁

自适应自旋锁,就是等待线程空循环次数并非是固定的.而是会动态的根据情况来改变自旋次数.自旋次数由上次获取锁的状态来决定.

原理

1:如果抢锁线程在同一个对象上之前成功的获取过锁,那么JVM会认为这次自旋也很有可能获取到锁,所以自旋的次数会多一些.

2:如果对于某个锁,抢锁线程很少获取到锁,那么JVM就会减少这个线程的自旋次数,甚至省去自旋过程,避免浪费处理器资源.

自适应自旋锁主要解决的是锁竞争时间不确定的问题.根据上一次自旋次数和结果来调整下一次自选的次数.

轻量级锁的膨胀

轻量级锁是为了减少多线程抢锁情况下进入操作系统底层的互斥锁的概率,并不是要去替代操作系统的互斥锁.所以在竞争激烈的情况下,轻量级锁会膨胀为重量级锁.

时间一分一秒的流逝,我们又留下了什么.是一身反骨,还是甘愿被磨平棱角.

如果大家喜欢我的分享的话,可以关注下我的微信公众号

心有九月星辰

 

标签:实战,Word,synchronized,Mark,线程,轻量级,自旋,锁线
From: https://blog.csdn.net/m0_68082638/article/details/141285773

相关文章

  • 【Mudo】实战项目之应用层模块
    文章目录前言正文1.Util1.1File1.2Url1.3Str1.4Infor2.Http2.1Request2.2Response2.3Context2.4Server尾序前言在上一篇文章当中,博主从代码的层面介绍了服务器模块的实现,最终封装出了一个传输层的TcpServer模块,那么在本篇将进一步向上拓展出一个应用层......
  • 使用TF-IDF进行情感分析的实战指南
    随着自然语言处理(NLP)的迅速发展,情感分析作为其中的重要应用领域,越来越受到关注。无论是社交媒体的舆情分析、用户评论的情感判断,还是企业年报的情感倾向识别,情感分析在多个领域都有广泛的应用。本文将聚焦于如何利用TF-IDF(TermFrequency-InverseDocumentFrequency)技术进......
  • 《亿级流量系统架构设计与实战》第十一章 Timeline Feed服务
    TimelineFeed服务一、概述1、分类2、功能二、设计原理1、拉模式与用户发件箱2、推模式与用户收件箱3、推拉模式结合三、关键技术1、内容与用户收件箱的交互(推模式)2、推送拆分子任务3、收件箱模型设计内容总结自《亿级流量系统架构设计与实战》一、概述1、分......
  • 短视频 IP实战课,独创一键复制学习秘籍,转战新领域,月赚五万轻松行
    1.引言随着数字媒体的快速发展,短视频已成为内容传播和个人品牌建设的重要平台。对于寻求职业转型的专业人士而言,短视频IP运营不仅提供了灵活的工作时间,还有望带来可观的经济回报和与行业精英交流的机会。然而,这一新兴领域也存在诸多挑战,如行业门槛、专业技能缺乏等。2.短......
  • ps通道实战
    目标把图片的数和草扣取出来,然后加上背景色 步骤一找到通道的蓝色 复制图层 2.crl+l打开色阶 3.画笔处理下白点的部分 4.ctrl+i反选选区 重点,选中不是创建选区,这时我们应该手工创建选区,按ctrl+点击蓝拷贝5.回到图层,给图层创建蒙版6.添加图层背景色添加蓝色......
  • DrissionPage实战之采集猫眼电影top100榜
    在当今信息化的时代,数据的获取与分析变得愈发重要。电影行业作为一个充满活力的领域,吸引了大量观众和投资者。猫眼电影作为中国领先的电影票务平台之一,提供了丰富的电影信息和用户评价,成为了研究电影市场趋势和观众偏好的重要数据源。通过抓取猫眼电影的Top100榜单,开发者可以......
  • PyTorch深度学习实战(18)—— 可视化工具
    在训练神经网络时,通常希望能够更加直观地了解训练情况,例如损失函数曲线、输入图片、输出图片等信息。这些信息可以帮助读者更好地监督网络的训练过程,并为参数优化提供方向和依据。最简单的办法就是打印输出,这种方式只能打印数值信息,不够直观,同时无法查看分布、图片、声音等......
  • 无涯 - 服务端性能测试实战 - Linux中IO系统负载瓶颈分析
    模拟100个进程不断的进行IO读写 执行后,看IO负载,呈上升趋势 查看进程的资源信息 iowait一直呈上升趋势,且系统资源sys也一直呈上升趋势 查找一直占用IO的进程PID  数入命令,htop此时可以看到CPU平均负载也呈上升趋势 将资源占用比较多的进程kill掉......
  • fpga图像处理实战-对数变换
    对数变换        图像对数变换(LogarithmicTransformation)是一种非线性灰度变换方法,常用于增强图像中的细节,特别是在图像中存在较大的亮度范围时。对数变换通过压缩亮度范围,使得较暗区域的细节更加明显,同时抑制过亮区域的影响。它在图像处理中的应用非常广泛,特别是在......
  • fpga图像处理实战-图像缓存(FIFO)
    FPGA实现`timescale1ns/1ps////Company://Engineer:////CreateDate:2024/08/1813:47:22//DesignName://ModuleName:line_buffer//ProjectName://TargetDevices://ToolVersions://Description:////Dependencies:////Revision......