首页 > 其他分享 >Synchronized重量级锁原理和实战(五)

Synchronized重量级锁原理和实战(五)

时间:2024-08-22 08:55:29浏览次数:11  
标签:实战 Cxq Synchronized Thread lock 线程 内核 EntrlList 重量级

在JVM中,每个对象都关联这一个监视器,这里的对象包含可Object实例和Class实例.监视器是一个同步工具,相当于一个凭证,拿到这个凭证就可以进入临界区执行操作,没有拿到凭证就只能阻塞等待.重量级锁通过监视器的方式保证了任何时间内只允许一个线程通过监视器保护的临界区代码.

重量级锁的核心原理

JVM每个对象都有一个监视器,监视器随着对象一起创建 销毁.本质上监视器是一种同步工具,也可以说是一种同步机制.

1:同步.监视器所保护的临界区代码都是互斥执行的.一个监视器一个凭证,任何一个线程执行临界区代码都需要获取凭证,执行完了释放许可.

2:协作.监视器提供Signal机制,允许正在持有许可的线程释放凭证进入阻塞等待状态,等待其他线程发送Signal去唤醒.其他拥有凭证的线程可以唤醒正在阻塞等待的线程,让它可以重新获得凭证执行临界区代码.

在虚拟机中,监视器是由C++类ObjectMonitor实现的.(我对C++不是很熟,就简单介绍下,有个印象知道如何实现,如果对技术很感兴趣,可以去学学C++)

ObjectMonitor类中关键的属性是Owner(_owner) WaitSet(_WaitSet) Cxq(_cxq) EntrlList(_EntrlList).好好品味,显示锁实现原理和这个相同.

WaitSet Cxq EntrlList说明

Cxq:竞争队列,所有请求锁的线程首先被放到竞争队列里.

EntrlList:Cxq中那些有资格成为候选资源的线程被移到EntrlList中.

WaitSet:某个拥有ObjectMonitor的线程在调用Object.wait()方法之后被阻塞,然后该线程被放置在WaitSet列表中.

ObjectMonitor内部抢锁流程

1:Cxq

Cxq并不是一个真正的队列,只是一个虚拟队列,原因在于Cxq是由Node及其next指针逻辑构成,并不存在一个队列数据结构,(我自己的理解是引用,就好比1引用2,2引用3,以此类推).

每次新加入Node会在Cxq的队头进行,通过CAS改变第一个节点的指针为新增节点.同时新增节点的next指针指向后续节点.从Cxq取元素时,会从队尾获取.可以看出来,Cxq是一个无锁结构.

因为只有Owner线程才能从队尾获取元素,所以线程出队没有竞争,也避免了ABA问题.还有线程在进入Cxq之前,会通过CAS操作进行一次抢锁,获取不到才会进入队列,所以重量级锁是一个非公平锁.

2:EntrlList

EntrlList与Cxq在逻辑上都属于等待队列.Cxq会被线程并发访问为了降低对Cxq队尾的竞争,而建立了EntrlList,在Owner线程释放锁的时候,JVM会从Cxq中迁移线程到EntrlList,并会指定EntrlList中的某个线程(一般为头线程)为OnDeck Thread(Ready Thread).EntrlList里的线程作为候选者线程存在.

3:OnDeck Thread 与 Owner Thread

JVM不直接把锁传递给Owner Thread,而是把锁竞争的权利交给OnDeck Thread,OnDeck需要重新竞争锁,这样虽然牺牲了一定的公平性,但是提高了吞吐量.在JVM中也把这种行为叫做竞争切换.

OnDeck Thread线程获取到锁后会变为Owner Thread,无法获取锁的OnDeck Thread依然会留在EntrlList中,考虑到公平性,OnDeck Thread在队列中的位置不会变.

在OnDeck Thread成为Owner Thread的过程中还有一个不公平的事情,就是后来新抢锁的线程有可能直接获取锁成为WaitSet.

4:WaitSet

如果Owner Thread调用了Object.wait()方法之后就会进入WaitSet队列.直到某个时刻调用Object.notify()方法或者Object.notifyAll()唤醒,线程会重新进入EntrlList队列.

重量级锁开销

处于ContentionList EntrlList WaitSet中的线程都处于阻塞状态.线程的阻塞和唤醒都需要操作系统来帮忙.Linux内核下采用pthread_mutex_lock系统调用实现,进程要从用户态切换到内核态.

Linux系统的体系分为用户态和内核态.

Linux系统的内核是一组特殊的软件程序,负责控制计算机硬件资源.例如协调CPU资源,分配内存资源,并且提供稳定的环境供程序运行.应用程序的活动空间为用户空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源 存储资源 IO资源等.

用户态与内核态有各自专用的内存空间 专用的寄存器等.进程从用户态切换至内核态需要传递许多变量 参数给内核,内核也需要保护好用户态在切换时的一些寄存器值 变量等.以便内核态调用结束后可以切换回用户态继续工作.

用户态的进程能够访问的资源受到了极大的控制,而运行在内核态的进程可以为所欲为.一个进程可以运行在用户态也可以运行在内核态,它们之间肯定有切换的方式.

用户态切换内核态的方式

1:硬件中断.硬件中断也称为外设中断,当外设完成用户请求时,会向CPU发送中断信号.

2:系统调用.其实系统调用本身就是中断,只不过是软件中断,与硬件中断不同.

3:异常.如果当前进程运行在用户态,这时发生了异常事件(例如缺页异常),就会触发切换.

用户态是应用程序运行的空间,为了能访问到内核管理的资源(例如CPU 内存 IO),可以通过内核态所提供的的访问接口实现,这些接口叫做系统调用.pthread_mutex_lock系统调用是内核态为用户态提供的Linux内核态下互斥锁的访问机制,所以使用pthread_mutex_lock系统调用时,进程需要从用户态切换到内核态,这种切换要消耗很多的时间,有可能比用户执行代码的时间还长.

重量级锁演示

public class HeavyWeightLockTest {

    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("抢锁前objectLock状态:");
        objectLock.printObjectStruct();
        Thread.sleep(5000);
        CountDownLatch latch = new CountDownLatch(3);

        Runnable runnable = () -> {
            for (int i = 0; i < MAX_TURN; i++) {
                synchronized (objectLock) {
                    objectLock.increase();
                    if (i == 0) {
                        System.out.println("第一个线程抢锁,lock的状态为:");
                        objectLock.printObjectStruct();
                    }
                }
            }
            latch.countDown();
            for (int j = 0; ; j++) {
                LockSupport.parkNanos(10);
            }
        };
        new Thread(runnable).start();
        
        LockSupport.parkNanos(2000);

        Runnable lightWeightRunnable = () -> {
            for (int i = 0; i < MAX_TURN; i++) {
                synchronized (objectLock) {
                    objectLock.increase();
                    if (i == 0) {
                        System.out.println(Thread.currentThread().getName()
                         + "占有锁,lock的状态为:");
                        objectLock.printObjectStruct();
                    }
                    LockSupport.parkNanos(10);
                }
            }
            latch.countDown();
        };

        //启动两个线程开始抢锁.
        new Thread(lightWeightRunnable, "抢锁线程-1").start();
        Thread.sleep(5000);
        new Thread(lightWeightRunnable, "抢锁线程-2").start();
        latch.await();
        LockSupport.parkNanos(2000);
        System.out.println("释放锁,lock的状态为:");
        objectLock.printObjectStruct();
    }
}

可以看出lock标记位为偏向锁状态,还没有偏向线程.

 

可以看出lock标记位101为偏向状态,并且有了偏向线程.

 

可以看出lock标记位000已经不是偏向锁,升级为了轻量级锁.

 

可以看出lock标记位变为了010成为了重量级锁. 

我一直在努力,被质疑,被嘲讽.在沮丧,还是会在第二天早晨一如既往的去战斗.我坚信我会成功.

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

心有九月星辰

标签:实战,Cxq,Synchronized,Thread,lock,线程,内核,EntrlList,重量级
From: https://blog.csdn.net/m0_68082638/article/details/141368267

相关文章

  • TypeScript深度揭秘:Map的全方位详解、作用、特点、优势及实战应用和高级应用
            在TypeScript的广阔世界里,Map对象无疑是一个强大的存在,它提供了灵活且高效的键值对存储机制。今天,我们就来一场轻松而严谨的探秘之旅,全方位解析TypeScript中Map的定义、作用、特点、优势,并通过实战代码示例,带你领略Map的无穷魅力。引言Map是TypeScript(以及Ja......
  • Spring Boot实战:使用模板方法模式优化数据处理流程
    概述在软件开发过程中,我们经常需要处理各种各样的数据,这些数据可能来自不同的源,比如数据库、文件系统或者外部API等。尽管数据来源不同,但很多情况下处理这些数据的步骤是相似的:读取数据、清洗数据、转换数据格式、存储结果等。为了提高代码的复用性和可维护性,我们可以利用设计......
  • 《Python数据分析实战》
    环境搭建定义变量名时要遵循的规则:变量名必须以字母或下画线开始,名字中间只能由字母、数字和下画线组成长度不能超过255个字符变量名在有效范围内必须具有唯一性不能使用保留字(关键字)区分大小写不能对元组中的元素做修改,只能做切片查询。如果元组中只有1个元素,则需要在这......
  • 《Prometheus监控实战》读书笔记
    监控简介Google服务层次结构图,监控是底座一些监控反模式:事后监控机械式监控不(够)准确的监控静态监控:不是说超过某个绝对阈值系统就一定出现问题,更有意义的监控是对比(环比)动态监控。数据库性能分析供应商VividCortex的首席执行官BaronSchwartz对此评论道[插图]:它们比一个停......
  • MySQL子查询、WITH AS、LAG查询统计数据实战
    需求给出一个比较常见的统计类业务需求:统计App(包括iOS和Android两大类)每日新注册用户数、以及累计注册用户数。数据库采用MySQL,根据上面的需求,不难设计表如下:createtableos_day_count(stat_datevarchar(10)notnullcomment'统计日期',osvarcha......
  • 从零到一,全面掌握Apache DolphinScheduler发版流程,实战派经验分享!
    引言ApacheDolphinScheduler的发版流程对于确保软件质量和社区协作至关重要,社区Committer王兴杰为我们详细介绍了ApacheDolphinScheduler的发版流程,包括环境准备、流程文档、基础工具准备、依赖包确认等关键步骤,并指出了发版流程中可能会遇到的麻烦以及相应的解决方案,欢迎学习......
  • 豆瓣评分9.0!Python3网络爬虫开发实战,堪称教学典范!
    今天我们所处的时代是信息化时代,是数据驱动的人工智能时代。在人工智能、物联网时代,万物互联和物理世界的全面数字化使得人工智能可以基于这些数据产生优质的决策,从而对人类的生产生活产生巨大价值。在这个以数据驱动为特征的时代,数据是最基础的。数据既可以通过研发产品获得,......
  • SAFe大规模敏捷企业级实战演练培训
    ​课程简介SAFe–ScaledAgileFramework是目前全球运用最广泛的大规模敏捷框架,也是成长最快、最被认可、最有价值的规模化敏捷框架,目前全球SAFe认证专业人士已达80万人,福布斯100强的70%都在实施SAFe。本课程是一个2天的SAFe权威培训课程,在课程中,学员将系统地学习大规模敏捷......
  • 一次实战压测流程及问题梳理
    1、背景及目的在动手之前,先要想清楚我们期望从压测中获取的价值是什么。这次压测的背景,主要是为了应对旺季到来,避免旺季的大量流量和高并发造成服务不可用,提升稳定性。而在稳定性建设中,也会从事前、事中及事后来看,包含的维度包含风险识别、监控告警、应急流程及故障复盘等不同的......
  • 【python】Python实现XGBoost算法的详细理论讲解与应用实战
    ✨✨欢迎大家来到景天科技苑✨✨......