偏向锁、轻量级锁、自旋锁、重量级锁,它们都是什么?有什么关联

news2024/12/25 9:27:23

互斥锁的本质是共享资源。
当有多个线程同时对一个资源进行操作时,为了线程安全,要对资源加锁。
更多基础内容参看上文《深入了解Java线程锁(一)》

接下来,我们来看看两个线程抢占重量级锁的情形:
在这里插入图片描述

上图讲述了两个线程ThreadA和ThreadB两个线程在争抢锁,
ThreadA进入来临界区,可以执行资源代码;
ThreadB没有抢到锁,进入BLOCKED队列,等待唤醒。
当ThreadA执行完后,释放锁,去唤醒ThreadB。
这是重量级锁的流程。
因为它会带来阻塞和唤醒,会带来开销,所以叫“重量级”锁。


加锁会带来性能开销,Java 1.6之前只有重量级锁。
Java 1.6 之后做了优化,出现了“轻量级锁 和 偏向锁”,能够减少重量级锁的获得和释放的性能开销。

我们在线程竞争的情况下,才会用重量级锁来处理。
我们是否能在加重量级锁之前,先去试着判断一下,是否存在竞争呢?
如果不存在,就不必使用重量级锁了,就可以带来性能优化了。


锁的升级
在这里插入图片描述
ThreadA 和ThreadB 加锁不是一步到位直接重量级锁。
而是先通过偏向锁抢占,当A获取来偏向锁后,B去抢占;
如果不成功,B会升级到轻量级锁继续尝试抢占;
如果不成功,B会去重量级锁抢占;
如果不成功,进入阻塞队列,等待唤醒。

线程最终的结果是要实现互斥的特性,我们希望能在线程B阻塞之前抢占到锁,如果可以,那么就不需要进行重量级锁的BLOCKED和唤醒,就会提高性能。

加锁一定会带来性能开销,怎么优化?—— 不加锁!(在不加锁的情况下解决线程安全问题)
锁的升级以及优化的目的,就是如何能在线程B不阻塞的情况下也能抢占到锁。

偏向锁

偏向锁是偏向的特性。
如果我们某个线程获取的锁是偏向锁,在对象头中会存储线程ID,偏向锁标志为1
在这里插入图片描述
偏向锁是不存在竞争的情况下可以使用。
名字叫做“偏向”就是当前偏向于某个线程,就将其线程ID存放在对象头中。
这种情况(加了锁但是没有竞争)比较少,所以默认情况下,偏向锁是关闭的。


我们来看 偏向锁的获得和撤销流程图
在这里插入图片描述
图中有两个线程A和B,中间是对象头。

  1. 线程A去访问同步代码块的时候,会检查对象头中是否存储了线程A,如果说没有存储的话,它会通过CAS替换对象头,把线程A的ID存储到对象头中。

CAS 相当于是一个乐观锁的概念,会去比较预期数据和原始数据是否一致,如果一致则修改,不一致则修改失败。CAS是原子性的操作。

  1. 替换成功的话,对象头中会存储A线程的ID和锁的标记 101

我们来实际看一下:
因为偏向锁默认关系,所以要先打开偏向锁:
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
在这里插入图片描述

执行没有线程抢占的代码:(与此代码相关背景请见《深入了解JAVA线程锁(一)》)

import org.openjdk.jol.info.ClassLayout;

public class MyClassLayout {
    public static void main(String[] args) {
        MyClassLayout myClassLayout = new MyClassLayout();
        synchronized (myClassLayout) {
            System.out.println(ClassLayout.parseInstance(myClassLayout).toPrintable());
        }
    }
}

执行结果为:

demo.MyClassLayout object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 a0 c5 45 (00000101 10100000 11000101 01000101) (1170579461)
      4     4        (object header)                           cb 01 00 00 (11001011 00000001 00000000 00000000) (459)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

我们可以看到当前是 101,是偏向锁。


  1. 线程A可以执行同步代码块。
  2. 此时如果有线程B在线程A已经获得偏向锁的情况下,访问同步代码块。
  3. 检查对象头中是否存储了线程B,如果没有的话,使用CAS替换对象头,可能成果也可能失败。
  4. 如果替换失败的话,线程B就要撤销原来A获得的偏向锁。
  5. 线程B暂停线程A,进行撤销。这里的撤销有两种,一种是撤销,一种是升级。
  6. 如果撤销成果,则线程B会获得偏向锁。什么情况可以成功撤销:
    ① 线程A已经执行结束;
    ② 线程A处于全局安全点,表示已经没有指令在运行。
  7. 如果有线程A的指令在执行的话,就无法撤销(即撤销失败)。那么就会升级到轻量级锁,会把对象头标记改为轻量级锁的标记。

偏向锁中没有阻塞,只有一个CAS。
轻量级锁中也没有阻塞,而是有个重试,即多次CAS(自旋锁)。

我们来看轻量级锁的流程图
在这里插入图片描述

  1. 同时两个线程A、B过来。
  2. 线程A进来访问同步代码块,会在线程的栈针中分配一个rock record空间,把对象头复制到rock record空间里。
    把当前的指针,指向栈中锁记录。
    在这里插入图片描述
  3. 抢占锁在轻量级锁范围内所做的事就是通过CAS修改 Mark Word中指向 lock record 的指针。 如果成功,表示当前线程A获得轻量级锁,它会把对象头存储指向栈中记录的指针。状态是 00
  4. 因为当前锁已被线程A获得,线程B竞争失败,它会有重试机制。(轻量级锁的多次CAS就是体现中这里,它叫自旋锁)

很多情况下,线程获得锁再释放锁的时间很短。这种情况下,我们通过重试去抢占,相比通过阻塞去抢占,更划算,所以通过重试去达到抢占锁的目的。
但是重试也是有次数的,不能无限重试。在JDK1.6之前,自旋锁默认重试10次。JDK1.6之后,引入来自适应自旋锁,会根据上一次抢占锁时间长,这次自适应时间长一些,否则会短一些。

  1. 如果线程B自旋失败,就会锁膨胀,升级为重量级锁。

Synchronized是个非公平锁,允许插队。

在这里插入图片描述
5. 重量级锁有个 Monitor 监视器,线程A、B会进行 monitor enter的抢占,
6. 如果线程A在monitor enter中抢占成功,就会持有锁对象,可以执行资源代码块。
7. 这时,没有抢到锁的线程B,会阻塞,然后放入到队列中。
8. 当线程A执行完资源代码释放后,阻塞的线程B会被唤醒,会进行新一轮的抢占。


我们通过代码来看锁膨胀的示例:

public class MyLockDemo {
    public static void main(String[] args) throws InterruptedException {
        MyLockDemo myLockDemo = new MyLockDemo();
        new Thread(()->{
            synchronized (myLockDemo){
                System.out.println("Thread-A caught the lock.");
                System.out.println(ClassLayout.parseInstance(myLockDemo).toPrintable());
            }
        },"Thread-A").start();

        //sleep 10秒,则是轻量级锁
        Thread.sleep(10000);

        synchronized (myLockDemo){
            System.out.println("main is catching.");
            System.out.println(ClassLayout.parseInstance(myLockDemo).toPrintable());
        }
    }
}

执行上述代码,结果为:

Thread-A caught the lock.
LockDemo.MyLockDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           d0 f1 1f 62 (11010000 11110001 00011111 01100010) (1646260688)
      4     4        (object header)                           1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

main is catching.
LockDemo.MyLockDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           38 f7 af 60 (00111000 11110111 10101111 01100000) (1622144824)
      4     4        (object header)                           1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


Process finished with exit code 0

我们可以看到最末三位是 000,所以是轻量级锁。
接下来,我们把代码中的sleep 注释掉,再看结果:

main is catching.
LockDemo.MyLockDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           0a a1 70 b3 (00001010 10100001 01110000 10110011) (-1284464374)
      4     4        (object header)                           a8 02 00 00 (10101000 00000010 00000000 00000000) (680)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Thread-A caught the lock.
LockDemo.MyLockDemo object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           0a a1 70 b3 (00001010 10100001 01110000 10110011) (-1284464374)
      4     4        (object header)                           a8 02 00 00 (10101000 00000010 00000000 00000000) (680)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


Process finished with exit code 0

这时我们发现末尾三位是 010,是重量级锁无疑了。

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

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

相关文章

SMART PLC斜坡函数功能块(梯形图代码)

斜坡函数Ramp的具体应用可以参看下面的文章链接: PID优化系列之给定值斜坡函数(PLC代码+Simulink仿真测试)_RXXW_Dor的博客-CSDN博客很多变频器里的工艺PID,都有"PID给定值变化时间"这个参数,这里的给定值变化时间我们可以利用斜坡函数实现,当然也可以利用PT1…

vb.net 视频音频转换

视频&音频转换工具 V23.0主流视频音频转换工具,Kbps数值越大,音频品质越高(前提原视频或音频文件品质高)。.NETFramework V4.0点击按钮 选中文件 保存文件 即可转换,转换速度较快,转换后的音频文件未发…

Detr源码解读(mmdetection)

Detr源码解读(mmdetection) 1、原理简要介绍 整体流程: 在给定一张输入图像后,1)特征向量提取: 首先经过ResNet提取图像的最后一层特征图F。注意此处仅仅用了一层特征图,是因为后续计算复杂度原因,另外&am…

使用kubeadm 部署kubernetes 1.26.1集群 Calico ToR配置

目录 机器信息 升级内核 系统配置 部署容器运行时Containerd 安装crictl客户端命令 配置服务器支持开启ipvs的前提条件 安装 kubeadm、kubelet 和 kubectl 初始化集群 (master) 安装CNI Calico 集群加入node节点 机器信息 主机名集群角色IP内…

DS期末复习卷(十)

一、选择题(24分) 1&#xff0e;下列程序段的时间复杂度为&#xff08; A &#xff09;。 i0&#xff0c;s0&#xff1b; while (s<n) {ssi&#xff1b;i&#xff1b;} (A) O(n^1/2) (B) O(n ^1/3) © O(n) (D) O(n ^2) 12…xn xn^1/2 2&#xff0e;设某链表中最常用的…

SnowFlake 雪花算法和原理(分布式 id 生成算法)

一、概述 SnowFlake 算法&#xff1a;是 Twitter 开源的分布式 id 生成算法。核心思想&#xff1a;使用一个 64 bit 的 long 型的数字作为全局唯一 id。算法原理最高位是符号位&#xff0c;始终为0&#xff0c;不可用。41位的时间序列&#xff0c;精确到毫秒级&#xff0c;41位…

Android 原生 TabLayout 使用全解析

前言为什么会有这篇文章呢&#xff0c;是因为之前关于TabLayout的使用陆陆续续也写了好几篇了&#xff0c;感觉比较分散&#xff0c;且不成体系&#xff0c;写这篇文章的目的就是希望能把各种效果的实现一次性讲齐&#xff0c;所以也有了标题的「看这篇就够了」。TabLayout作为…

【自然语言处理】Topic Coherence You Need to Know(主题连贯度详解)

Topic Coherence You Need to Know皮皮&#xff0c;京哥皮皮&#xff0c;京哥皮皮&#xff0c;京哥CommunicationUniversityofChinaCommunication\ University\ of\ ChinaCommunication University of China 在大多数关于主题建模的文章中&#xff0c;常用主题连贯度&#xff…

JSP实现数据传递与保存(一)

学习目标&#xff1a; 理解JSP内置对象的概念 掌握request和response的使用 掌握转发和重定向的区别 掌握out对象的使用 学习内容&#xff1a; 1.HTML页面转成JSP页面 HTML页面转成JSP页面一般有两种方式 方式1&#xff1a;直接修改HTML页面 1&#xff09;直接在HTM…

QT+OpenGL模板测试和混合

QTOpenGL模板测试和混合 本篇完整工程见gitee:QtOpenGL 对应点的tag&#xff0c;由turbolove提供技术支持&#xff0c;您可以关注博主或者私信博主 模板测试 当片段着色器处理完一个片段之后&#xff0c;模板测试会开始执行。和深度测试一样&#xff0c;它可能会丢弃片段&am…

Win11的两个实用技巧系列之Win11怎么找回Win7照片查看器

Win11怎么找回Win7照片查看器? Win11旧版照片查看器的切换方法Win11怎么找回Win7照片查看器&#xff1f;用习惯了win7的照片查看器&#xff0c;想要在win11中使用&#xff0c;该怎么启用旧版照片查看器呢&#xff1f;下面我们就来看看Win11旧版照片查看器的切换方法Win11系统启…

c++之二叉树【进阶版】

前言 在c语言阶段的数据结构系列中已经学习过二叉树&#xff0c;但是这篇文章是二叉树的进阶版&#xff0c;因为首先就会讲到一种树形结构“二叉搜索树”&#xff0c;学习二叉搜索树的目标是为了更好的理解map和set的特性。二叉搜索树的特性就是左子树键值小于根&#xff0c;右…

【JVM】运行时数据区与对象的创建流程

4、运行时数据区 4.1、运行时数据区介绍 运行时数据区也就是JVM在运⾏时产生的数据存放的区域&#xff0c;这块区域就是JVM的内存区域&#xff0c;也称为JVM的内存模型——JMM 堆空间&#xff08;线程共享&#xff09;&#xff1a;存放new出来的对象 元空间&#xff08;线程共…

3,预初始化(一)(大象无形9.2)

正如书中所说&#xff0c;预初始化流程由FEngineLoop::PreInit()所实现 主要处理流程 1&#xff0c;设置路径&#xff1a;当前程序路径&#xff0c;当前工作目录路径&#xff0c;游戏的工程路径 2,设置标准输出&#xff1a;设置GLog系统输出的设备&#xff0c;是输出到命令行…

web自动化测试-执行 JavaScript 脚本

JavaScript 是一种脚本语言&#xff0c;有的场景需要使用 js 脚本注入辅助我们完成 Selenium 无法做到的事情。 当 webdriver 遇到无法完成的操作时&#xff0c;可以使用 JavaScript 来完成&#xff0c;webdriver 提供了 execute_script() 方法来调用 js 代码。 执行 js 有两种…

Leetcode.2385 感染二叉树需要的总时间

题目链接 Leetcode.2385 感染二叉树需要的总时间 Rating &#xff1a; 1711 题目描述 给你一棵二叉树的根节点 root&#xff0c;二叉树中节点的值 互不相同 。另给你一个整数 start。在第 0分钟&#xff0c;感染 将会从值为 start的节点开始爆发。 每分钟&#xff0c;如果节点…

一文3000字从0到1实现基于requests框架接口自动化测试项目实战(建议收藏)

requests库是一个常用的用于http请求的模块&#xff0c;它使用python语言编写&#xff0c;在当下python系列的接口自动化中应用广泛&#xff0c;本文将带领大家深入学习这个库 Python环境的安装就不在这里赘述了&#xff0c;我们直接开干。 01、requests的安装 windows下执行…

大数据常见应用场景及架构改进

大数据常见应用场景及架构改进大数据典型的离线处理场景1.大数据数据仓库及它的架构改进2.海量数据规模下的搜索与检索3.新兴的图计算领域4.海量数据挖掘潜在价值大数据实时处理场景大数据典型的离线处理场景 1.大数据数据仓库及它的架构改进 对于离线场景&#xff0c;最典型…

磷脂-聚乙二醇-丙烯酸酯;DSPE-PEG-AC试剂说明;DSPE-PEG-Acrylate科研用

中文名称&#xff1a;磷脂-聚乙二醇-丙烯酸酯 丙烯酸酯-聚乙二醇-磷脂 简称&#xff1a;DSPE-PEG-AC&#xff1b;DSPE-PEG-Acrylate 溶剂&#xff1a;溶于部分常规有机溶剂 PEG分子量:1000&#xff1b;2000&#xff1b;3400&#xff1b;5000等等 注意事项&#xff1a;避免…

JavaSE02-JVM、JRE、JDK

文章目录一、JVM、JRE、JDK区别二、JDK的安装和配置1.JDK安装2.测试验证3.环境变量配置3.1 配置JAVA_HOME系统变量3.2 配置Path环境变量再最前面加上&#xff1a; %JAVA_HOME%\bin一、JVM、JRE、JDK区别 JVM&#xff08;Java Virtual Machine&#xff09;&#xff0c;Java虚拟…