聊聊无锁、匿名偏向锁、偏向锁、轻量级锁、重量级锁

news2024/12/26 11:58:00

锁的竞争可以理解是markword的竞争。

一、简介

本文做作为知识点的补充,有些情况并没有进行测试。

二、markword结构图

64位虚拟机markword结构图:

 三、锁的多种状态

我们一般认为锁的状态是:无锁、偏向锁、轻量级锁、重量级锁;

锁的升级流程:无锁-》偏向锁-》轻量级锁-》中重量级锁;

一些东西并非像我们了解的那样,至少我认为这个过程介绍的并不完整,并且还有坑,不知是否有一样的人和我掉入坑的小伙伴。


锁的状态应该分为: 无锁、匿名偏向锁、偏向锁、轻量级锁、重量级锁。

  • 无锁: 0 01;
  • 匿名偏向锁: 1 01,但是偏向锁线程指针为空;
  • 偏向锁: 1 01,偏向锁线程指针指向当前持有线程;
  • 轻量级锁: 00;
  • 重量级锁: 10。

无锁-》偏向锁-》轻量级锁-》中重量级锁,流程中指的无锁不应该是 0 01无锁,而是匿名偏向锁,下面我们通过数据可以进行验证。

四、工具

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

JVM采用的是小端模式,需要现将其转换成大端模式,具体转换如下图所示:

可以先了解以下对象结构https://blog.csdn.net/sunboylife/article/details/114275416?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22114275416%22%2C%22source%22%3A%22sunboylife%22%7D

在这里插入图片描述

五、无锁和匿名偏向锁

HotSpot虚拟机默认开启偏向锁延迟BiasedLockingStartupDelay以前的默认值为4000,这将延迟锁定的使用延迟了4 s(4000 ms)

1.无锁 

public class NonLockTest {
    public static void main(String[] args) {
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }

}

可知打印的是 0 01 无锁态。

2.匿名偏向锁

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }

}

 

1 01是偏向锁的标识,后方指向线程,这种情况就是上文所说的匿名偏向锁。

当然我们也可以通过设置偏向锁延迟时间为0,不用等待5秒直接打印上图结果,效果是一样的。

-XX:BiasedLockingStartupDelay=0

问题:为什么等待5秒后结果就不一样?

答: 1中存在偏向锁延迟,当执行代码的时候,偏向锁还未开启,因此是无锁状态;2中因为等待5秒后偏向锁已经开启,因此打印的是偏向锁状态,但由于没有发生线程占用,因此没有指向任何线程,所以此时的状态是匿名偏向锁。

3 无锁和匿名偏向锁的区别

3.1 无锁和匿名偏向锁的时机不同

由2可知

3.2 无锁与匿名偏向锁升级过程存在的区别

无锁

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
       // TimeUnit.SECONDS.sleep(5);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    }

}

可以看到无锁是直接升级为轻量级锁并没有进入偏向锁的状态。

匿名偏向锁

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    }

}

 可以看到匿名偏向锁是进入偏向锁的状态,并且指向了当前线程。

总结: 无锁态不会进入偏向锁态,而是直接进入轻量级锁态;匿名偏向锁会进入偏向锁态。

3.3 无锁和匿名偏向锁状态,执行同步代码释放锁后的状态区别

无锁

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
       // TimeUnit.SECONDS.sleep(5);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }

}

 

 未进入同步代码块前是无锁状态,进入代码块后是轻量级锁状态,最后退出同步代码块恢复到无锁状态。轻量级锁释放锁后,marworkd数据会恢复。

匿名偏向锁

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }

}

 

进入同步代码块之前是匿名偏向锁,进入同步代码块之后是偏向锁,退出同步代码块之后仍然保留偏向锁数据。偏向锁会释放但是markword数据不会恢复:也为同一个线程多次使用节省cas次数来提高性能,因此偏向锁是不可逆的。

 这里我们在拓展一下:

添加代码

new Thread(() -> {
    synchronized (object){
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}).start();

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(object).toPrintable());

        new Thread(() -> {
            synchronized (object){
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
            }
        }).start();
    }

}

 很明显子线程在使用锁的时候,主线程已经退出同步代码块并释放锁了

虽然主线程已经退出代码块了,子线程拿到锁后偏向锁还是升级为轻量级锁,并没有因为主线程释放偏向锁后子线程拿到的是偏向锁,而是拿到的是升级后的轻量级锁。

得出结论: 只要出现线程一个线程以上使用偏向锁,导致偏向锁升级为轻量级锁。

3.4 无锁与匿名偏向锁状态,同步代码块中使用hashcode的区别

无锁

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
       // TimeUnit.SECONDS.sleep(5);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
            object.hashCode();
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    }

}

 无锁态-》轻量级锁态-》重量级锁态,因为当同步代码块中调用hashcode的时候锁并未释放,但存在hashcode覆盖轻量级锁信息的风险,因此进行锁的升级来解决锁和hashcode并存的问题,

问题:为什么重量级锁可以解决hashcode和锁并存的问题,而轻量级锁不可以?

答:可能有人疑惑,持有轻量级的线程不是又拷贝markword吗?直接取不就行了,是的,这样理解是没问题的,但是栈是私有内存区域,如果是多线程的情况就不安全了,因为栈随时又弹出的时候;重量级之所以可以因为重量级锁会为每个对象在堆区创建一个Monitor实例

这里的互斥量实际就是ObjectMonitor的实现 Monitor。

ObjectMonitor类里有字段可以记录非加锁状态下的mark word,其中可以存储identity hash code的值。或者简单说就是重量锁可以存下identity hash code。

在HotSpot虚拟机中,Monitor是基于C++的ObjectMonitor类实现的,其主要成员包括:

 ObjectMonitor 的源代码

ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
//多线程竞争锁进入时的单向链表
_cxq = NULL ;
FreeNext = NULL ;
//_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
  • _owner:指向持有ObjectMonitor对象的线程
  • _WaitSet:存放处于wait状态的线程队列,即调用wait()方法的线程
  • _EntryList:存放处于等待锁block状态的线程队列
  • _count:约为_WaitSet 和 _EntryList 的节点数之和
  • _cxq: 多个线程争抢锁,会先存入这个单向链表
  • _recursions: 记录重入次数
  • _header:用来保存锁对象的mark word的值。因为object里面已经不保存mark word的原来的值了,保存的是ObjectMonitor对象的地址信息。当所有线程都完成了之后,需要销毁掉ObjectMonitor的时候需要将原有的header里面的值重新复制到mark word中来。
  • _object: 指向的是对象的地址信息,方便通过ObjectMonitor来访问对应的锁对象。

堆区正常都是共享内存块,因此多线程之间解决hashcode和持锁线程并存的问题,直接升级重量级锁。

匿名偏向锁

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
            object.hashCode();
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
    }

}

 和无锁的情况是一样的,相同解释。

因此匿名偏向锁更像平常说的无锁态,不管怎么样,根据每个人的习惯记忆吧。

3.5 关闭偏向锁二者会有什么区别

关闭偏向锁

-XX:-UseBiasedLocking

无锁

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
        //TimeUnit.SECONDS.sleep(6);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(object).toPrintable());

    }

}

偏向锁是否开启对无锁状态并没有什么影响

匿名偏向锁

因为偏向锁被关闭了,肯定也就是无锁状态了,因此和无锁的结果是一样的。

六、无锁

可通过关闭偏向锁、释放轻量级锁获得无锁状态。

七、匿名偏向锁

可通过开启偏向锁获得。

八、偏向锁

1.从3.3中可知,偏向锁不可逆;

2.无法从通过无锁态进行升级到偏向锁,只能通过匿名偏向锁进行升级成为偏向锁。

九、轻量级锁

1.从3.3中可知,轻量级锁释放后会回复到无锁状态,轻量级锁是可逆的;

2.从3.3中可知,偏向锁一个线程以上访问markword将从偏向锁升级为轻量级锁。

十、重量级锁

1.从3.4中可知,hashcode和锁记录竞争更新markword发生并存冲突时,会导致升级为重量级锁;

2.重量级锁释放后markword仍一直指向堆区Monitor实例,因此重量级锁是不可逆的;

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(6);
        User object = new User();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            object.hashCode();
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }

}

 多线程测试

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(6);
        User object = new User();
        System.out.println("主线程" + ClassLayout.parseInstance(object).toPrintable());
        synchronized (object){
            object.hashCode();
            System.out.println("主线程"+ ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println("主线程" + ClassLayout.parseInstance(object).toPrintable());

        new Thread(() ->{
            synchronized (object){
                System.out.println("子线程" + ClassLayout.parseInstance(object).toPrintable());
            }
            System.out.println("子线程" + ClassLayout.parseInstance(object).toPrintable());
        }).start();

    }

}

 主线程和子线程打印markword信号量指针相同。

3. 轻量级锁竞争失败升级为重量级锁

public class NonLockTest {
    public static void main(String[] args) throws InterruptedException {
       TimeUnit.SECONDS.sleep(6);
        User object = new User();
        System.out.println("主线程" + ClassLayout.parseInstance(object).toPrintable());
        //同步代码块后升级为偏向锁
        synchronized (object){
            System.out.println("主线程"+ ClassLayout.parseInstance(object).toPrintable());
        }



        new Thread(() ->{
            //step3, step3快于step2执行
            synchronized (object){
                //此时偏向锁升级为轻量级锁
                System.out.println("主线程竞争前,子线程状态" + ClassLayout.parseInstance(object).toPrintable());
                try {
                    //暂停等待主线程执行step2,但不释放锁资源
                    TimeUnit.SECONDS.sleep(10);
                    //由于主线程长时间未竞争成功,导致锁升级,因此此处应该是重量级锁状态
                    System.out.println("主线程竞争后,子线程状态" + ClassLayout.parseInstance(object).toPrintable());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();



        TimeUnit.SECONDS.sleep(2);
        //step2
        synchronized (object){
            //此时由于轻量级锁升级,打印重量级锁
            System.out.println("主线程竞争锁" + ClassLayout.parseInstance(object).toPrintable());
        }
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/861428.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot 整合MyBatis

整合MyBatis 官方文档&#xff1a;http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/ Maven仓库地址&#xff1a;https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.3 整合测试 导入 MyBatis 所需要的…

springboot工程集成前端编译包,用于uni-app webView工程,解决其需独立部署带来的麻烦,场景如页面->画布->图片->pdf

前端工程 访问方式 http://127.0.0.1:8080/context/frontEnd/index放行 public class SecurityConfig extends WebSecurityConfigurerAdapter { "/frontEnd/**",SysFrontEndController import lombok.extern.slf4j.Slf4j; import nl.basjes.shaded.org.springfram…

新零售智慧生态电商系统搭建,开源多用户商城系统开发(H5、Java)

搭建新零售智慧生态电商系统和开源多用户商城系统需要进行以下具体步骤&#xff1a; 1. 确定需求&#xff1a;首先明确系统的功能需求和技术要求&#xff0c;包括用户注册和登录、商品管理、购物车、订单管理、支付等功能。 2. 选择技术架构&#xff1a;确定使用的开发语言和…

C++笔记之将定时器加入向量并设置定时器的ID为i

C笔记之将定时器加入向量并设置定时器的ID为i code review! 文章目录 C笔记之将定时器加入向量并设置定时器的ID为i关于代码中的void operator()() 运行 代码 #include <chrono> #include <iostream> #include <thread> #include <vector>// 定义定时…

shell脚本变量

shell脚本变量 1.变量概述1.1变量类型1.2变量的命令要求1.3 变量作用范围1.4几个符号作用 2.局部变量3.环境变量3.1系统内置环境变量 4. 只读变量5.位置变量6.预定义变量7.四个配置文件作用 1.变量概述 变量即在程序运行过程中它的值是允许改变的量变量是用一串固定的字符串去…

(docker)mysql镜像拉取-创建容器-容器的使用【个人笔记】

【容器的第一次创建】 容器的第一次创建&#xff0c;需要先下载镜像&#xff0c;从 镜像拉取 0、可以搜索镜像的版本 docker search mysql1、先拉取MySQL的镜像&#xff0c;默认拉取最新版&#xff0c;使用下面的命令拉取mysql镜像 docker pull mysql也可以指定mysql的版本…

日撸java_day60

文章目录 小结k近邻算法&#xff08;knn&#xff09;定义算法流程距离度量k值的选择总结 聚类定义k-means聚类步骤k-means算法小结 小结 k近邻算法&#xff08;knn&#xff09; 定义 如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别…

Llms大模型中国开源项目大全(更新至2023-08-10)

一、前言 你了解中国ChatGPT相关开源项目的情况吗&#xff1f; 如果想要从事chatGPT相关项目的开发和研究&#xff0c;有哪些可以借鉴和开源项目&#xff1f; 中国的chatGPT开源项目发展如何&#xff0c;本文将给你带来答案。 二、项目概述 数据截至到&#xff1a;2023年8月1…

由于目标计算机积极拒绝,无法连接。 Could not connect to Redis at 127.0.0.1:6379

项目在启动时候报出redis连接异常 然后查看是redis 连接被计算机拒绝 解决方法 打开redis安装文件夹 先打开redis-servce.exe挂着&#xff0c;再打开redis-cli.exe 也不会弹出被拒接的问题了。而且此方法不用每次都去cmd里输入命令。

大连交通大学813软件工程考研习题

1.什么是软件生存周期模型?有哪些主要模型? 生存周期模型&#xff1a;描述软件开发过程中各种活动如何执行的模型。对软件开发提供强有力的支持&#xff0c;为开发过程中的活动提供统一的政策保证&#xff0c;为参与开发的人员提供帮助和指导&#xff0c;是软件生存周期模型…

【雕爷学编程】Arduino动手做(07)---旋转电位器模块之结构特点、作用、参数与测量

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

jstack 使用

.、用 ps -ef | grep -i java 命令&#xff0c;找出 Java|tomcat 进程 pid&#xff0c;用于查看全格式进程。 .、用 ps -aux | grep -i java 命令&#xff0c;找出 Java|tomcat 进程 pid&#xff0c;用于查看进程。.、用 top -Hp <pid> 命令&#xff0c;找出 CPU 占用最高…

【雕爷学编程】Arduino动手做(201)---搭建行空板开发环境之SSH连接

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

关于城市地下综合管廊运维火灾风险因素的探讨

安科瑞 华楠 摘要&#xff1a;随着城市基础设施的不断完善&#xff0c;地下综合管廊作为城市生命线工程得到了快速发展&#xff0c;综合管廊后期运维周期较长&#xff0c;如何有效保障管廊内各管线安全运行显得尤为重要。本文从地下综合管廊火灾的特点出发&#xff0c;根据燃烧…

【LeetCode】144. 二叉树的前序遍历、94. 二叉树的中序遍历、145. 二叉树的后序遍历

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 144. 二叉树的前序遍历 144. 二叉树的前序遍历 题目&#xff1a; 给你二叉树的根节点 root &…

开放式耳机推荐品牌,开放式耳机评测

​在忙碌的生活中&#xff0c;音乐成为我们放松心灵、享受美好的方式之一。而一副出色的蓝牙耳机&#xff0c;则能为我们带来沉浸式的音乐体验&#xff0c;仿佛让我们置身于音乐的海洋中。不入耳佩戴设计&#xff0c;耳挂在耳朵上&#xff0c;更舒适稳固&#xff0c;也更加干净…

16通道AD采集FMC子卡推荐哪些?

FMC149是一款16通道65MHz采样率14位直流耦合AD采集FMC子卡&#xff0c;符合VITA57.1规范&#xff0c;可以作为一个理想的IO模块耦合至FPGA前端&#xff0c;16通道AD通过FMC连接器&#xff08;HPC&#xff09;连接至FPGA从而大大降低了系统信号延迟。 该板卡支持板上可编程采样…

Vue3 —— reactive 全家桶及源码学习

该文章是在学习 小满vue3 课程的随堂记录示例均采用 <script setup>&#xff0c;且包含 typescript 的基础用法 前言 上一篇学习了 ref 全家桶&#xff0c;在此基础上一起学习下 reactive 全家桶 一、reactive 对比 ref ref 可以接收 所有类型&#xff0c;reactive 只…

W5500-EVB-PICO 做TCP Server进行回环测试(六)

前言 上一章我们用W5500-EVB-PICO开发板做TCP 客户端连接服务器进行数据回环测试&#xff0c;那么本章将用开发板做TCP服务器来进行数据回环测试。 TCP是什么&#xff1f;什么是TCP Server&#xff1f;能干什么&#xff1f; TCP (Transmission Control Protocol) 是一种面向连…

day7 8-牛客67道剑指offer-JZ74、57、58、73、61、62、64、65、把字符串转换成整数、数组中重复的数字

文章目录 1. JZ74 和为S的连续正数序列暴力解法滑动窗口&#xff08;双指针&#xff09; 2. JZ57 和为S的两个数字3. JZ58 左旋转字符串4. JZ73 翻转单词序列5. JZ61 扑克牌顺子6. JZ62 孩子们的游戏(圆圈中最后剩下的数)迭代 模拟递归 约瑟夫环问题 找规律 7. JZ64 求123...n8…