当前位置: 首页 > news >正文

12.轻量级锁原理及其实战

文章目录

  • 轻量级锁原理及其实战
    • 1.轻量级锁的核心原理
    • 2.轻量级锁的演示
      • 2.1.轻量级锁的演示代码
      • 2.2.结果分析
    • 3.轻量级锁的分类
      • 3.1.普通自旋锁
      • 3.2.自适应自旋锁
    • 4.轻量级锁的膨胀

轻量级锁原理及其实战

引入轻量级锁的主要目的是在多线程环境竞争不激烈的情况下, 通过CAS机制竞争锁减少重量级锁的产生的性能损耗,重量级锁使用了操作系统底层的互斥锁,会导致线程在用户态和核心态之间频繁切换,从而带来较大的性能损耗

1.轻量级锁的核心原理

轻量级锁存在目的就是尽可能的不去动用操作系统层面的互斥锁,因为性能较差。其实很多对象的锁的状态只会持续很短的一段时间,例如整数的自增运算,CPU很快就执行完毕了,在短时间内阻塞和唤醒线程这样显得值得,为此JDK引入的轻量级锁

轻量级锁其实就是一种自旋锁,因为JVM本身就是一种应用,所以希望在应用层面上通过自旋来解决这一类问题。

轻量级锁的执行过程,首先在 抢占锁线程进入临界区之前,如果内置锁没有被锁定,JVM首先在抢占锁线程中建立一个锁记录(Lock Record),用于存储对象目前的Mark Work拷贝

在这里插入图片描述

抢占锁线程首先处理好栈帧中的轻量级锁记录,然后通过CAS自旋,尝试将内置锁对象头的Mark Word 的 ptr_to_lock_record(锁记录指针),更新为抢占锁线程栈帧中锁记录的地址,如果这个更新执行成功了,这个线程就拥有了这个对象

然后JVM将Mark Word中lock的标志位改为 00(轻量级锁标志)

Mark Word的值被CAS更新后,包含锁对象信息的旧值就会被返回,这个时候需要抢占锁的线程找一个地方将旧 的Mark Word暂存起来。

在这里插入图片描述

锁记录是线程私有的,每个线程都有自己的一份锁记录,在创建完锁记录后,会将内置对象的MarkWord拷贝到锁记录的Displaced Mark Word字段,为什么这么做呢?

因为内置锁对象的Mark Word的结构会有所变化,Mark Word将会出现一个指向锁记录的指针,而不再存在无锁状态下的锁的哈希码等信息,所以必须将这些信息暂存起来,供后面锁释放的时候使用。

2.轻量级锁的演示

2.1.轻量级锁的演示代码

package com.hrfan.java_se_base.base.thread.jol;import com.hrfan.java_se_base.common.utils.SleepUtil;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.concurrent.CountDownLatch;/*** 轻量级锁演示*/
public class InnerLockTest {private static final Logger log = LoggerFactory.getLogger(InnerLockTest.class);@Test@DisplayName("测试轻量级锁的案例")public void test() {// 打印JVM信息log.error("JVM参数信息:{}", VM.current().details());SleepUtil.sleepMillis(5000);LightweightObjectLock lock = new LightweightObjectLock();// 打印抢占锁前 锁状态log.error("抢占锁前,lock状态");lock.printLockStatus();SleepUtil.sleepMillis(5000);CountDownLatch latch = new CountDownLatch(2);Runnable runnable = () -> {for (int i = 0; i < 1000; i++) {synchronized (lock){lock.increase();if (i == 1){log.error("第一个线程占有锁,lock状态!");lock.printLockStatus();}}}// 第一个线程执行完毕latch.countDown();// 线程虽然释放锁,但是一致存在死循环while (true){// 每次循环等待1msSleepUtil.sleepMillis(1);}};new Thread(runnable).start();// 等待1sSleepUtil.sleepMillis(1000);Runnable lightweightRunnable = () -> {for (int i = 0; i < 1000; i++) {synchronized (lock){lock.increase();if (i == 500){log.error("第二个线程占有锁,lock状态!");lock.printLockStatus();}// 每次循环等待1msSleepUtil.sleepMillis(1);}}// 循环执行完毕latch.countDown();};new Thread(lightweightRunnable).start();// 等待全部线程执行完毕try {latch.await();} catch (InterruptedException e) {throw new RuntimeException(e);}SleepUtil.sleepMillis(2000);log.error("释放锁后,lock状态!");lock.printLockStatus();}
}
class LightweightObjectLock{private static final Logger log = LoggerFactory.getLogger(MyObjectLock.class);private int count = 0;/*** 打印当前对象的一个状态*/public void printLockStatus(){log.error(ClassLayout.parseInstance(this).toPrintable());}/*** 将当前共享变量自增*/public void increase(){this.count++;}
}

2.2.结果分析

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.轻量级锁的分类

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

3.1.普通自旋锁

普通自旋锁,就是指当有线程来竞争时,抢占锁线程就会在原地循环等待,而不是阻塞,直到那个占有线程释放锁之后,这个抢占线程才可以获得锁。

默认情况下 自旋的次数为10次,可以通过 -XX:PreBlockSpin选项来进行修改

自旋锁的主要优势在于避免了线程的上下文切换和阻塞唤醒的开销,这对于一些锁竞争非常激烈但持有锁的时间很短的情况下,可以提升性能。然而,在锁竞争激烈且持有锁时间较长的情况下,自旋锁可能会导致资源的浪费和性能下降。

自旋锁的实现机制可以简单描述如下:

  1. 当线程尝试获取锁时,如果发现锁已被其他线程占用,则进入自旋等待状态,不断地检查锁是否被释放。
  2. 自旋等待通常是通过使用一个循环来实现的,循环中会不断地检查锁的状态。这里需要注意的是,自旋等待时需要使用一种合适的方式来避免过度消耗CPU资源,例如可以在循环中添加一些延迟操作或者进行自适应自旋等待。
  3. 当持有锁的线程释放锁时,其他线程中的一个线程会成功获取到锁,并继续执行下去。

需要注意的是,在使用自旋锁时需要考虑一些问题:

  • 自旋等待时间的设定:自旋等待时间过长会导致CPU资源的浪费,自旋等待时间过短则可能增加锁竞争的概率。合理设定自旋等待时间可以根据具体应用场景和硬件环境进行调整。
  • 自适应自旋等待:一些现代的锁实现中采用了自适应自旋等待的策略,根据锁竞争的情况动态调整自旋等待时间,以提高性能。
  • 超过自旋次数后的处理:如果一个线程在自旋等待一定次数后仍然无法获取到锁,可以选择进入阻塞状态,避免资源的浪费。这种策略被称为自旋锁的退化机制。

3.2.自适应自旋锁

自适应自旋锁,就是等待空循环的自旋次数并非是固定的,而是动态的根据实际情况来改变自旋的次数,自旋的次数由前一次在同一个锁上的自旋的时间以及锁的拥有着状态来决定,主要分为两种情况

  • 如果抢占线程在同一个锁对象上之前成功获得过锁,那么JVM就会认为i这次自旋也很有可能再次成功,因此允许自旋等待持续相对较长
  • 如果对于某个锁,抢占线程很少成功获得过,那么JVM将可能减少自旋等待时间甚至忽略自旋等待过程,以避免浪费处理器资源
    • JDK1.7后,轻量级锁使用的就是自适应自旋锁,JVM自动开启,且自旋时间由JVM自动控制
    • 轻量级锁也被称为 非阻塞同步、乐观锁,因为过程并没有吧线程阻塞挂起,而是线程空循环等待!

自适应自旋锁是一种改进的自旋锁实现方式,其根据前一次在同一个锁上的自旋时间以及锁的拥有者状态来动态调整自旋等待的次数。它的目标是在不同的锁竞争情况下,优化自旋等待的效果,减少资源的浪费。

具体实现自适应自旋锁的方式可能会因不同的JVM实现而有所差异,但其大致原理可以描述如下:

  1. 初始自旋次数:当一个线程第一次尝试获取锁时,JVM会给予一个初始的自旋次数。
  2. 自旋等待与锁竞争:线程在自旋等待期间持续尝试获取锁。如果线程在自旋等待期间成功获得了锁,那么JVM会根据前一次的自旋等待时间以及锁的拥有者状态来判断是否增加自旋次数。
  3. 自旋次数调整:根据前一次的自旋等待时间和锁的拥有者状态,JVM可能会逐渐增加或减少自旋等待的次数。如果前一次的自旋等待时间较长,表明自旋等待有效,JVM可能会增加自旋次数。如果前一次的自旋等待时间较短,表明自旋等待效果不佳,JVM可能会减少自旋次数或者直接放弃自旋等待。
  4. 自旋等待的退化:如果经过一定次数的自旋等待后仍然无法获得锁,JVM可能会将自旋等待退化为阻塞等待,避免资源的浪费。

自适应自旋锁的优势在于根据实际情况动态调整自旋等待的次数,可以在不同的锁竞争情况下提供更好的性能。通过根据前一次自旋等待时间和锁的拥有者状态进行自适应调整,可以更有效地利用处理器资源。然而,自适应自旋锁的具体实现可能会因JVM的不同版本和配置而有所差异,因此在具体应用中仍需考虑实际情况和性能需求来选择合适的锁实现方式。

4.轻量级锁的膨胀

轻量级锁膨胀是指在使用轻量级锁的过程中,如果锁竞争激烈或者存在其他特定情况,JVM会将轻量级锁膨胀为重量级锁。这种膨胀操作的目的是为了更好地处理锁竞争情况,确保多线程的安全性。

下面是轻量级锁膨胀的一般过程:

  1. 初始状态:当一个线程尝试获取轻量级锁时,JVM会先检查该对象是否被锁定。如果该对象未被锁定,JVM会将该对象的锁记录信息设置为指向当前线程的线程ID,并将对象头部的标记位设置为轻量级锁标记。
  2. 锁竞争:如果有另一个线程也尝试获取同一个对象的锁。在轻量级锁的设计中,如果锁竞争不激烈,JVM会使用自旋等待的方式,让竞争线程在自旋过程中等待锁的释放。
  3. 轻量级锁膨胀:如果锁竞争激烈或者其他特定情况发生,JVM会将轻量级锁膨胀为重量级锁。膨胀的过程包括以下步骤:
    • 锁记录的升级:JVM会将之前记录在对象头部的锁记录信息替换为指向重量级锁的指针。这个指针指向一个互斥量(如操作系统原生的互斥量)来实现线程的阻塞和唤醒。
    • 线程阻塞:竞争锁的线程会被阻塞,进入到等待队列中,等待锁的释放。
    • 线程唤醒:当持有锁的线程释放锁时,JVM会将等待队列中的一个线程唤醒,使其获取锁并继续执行。

膨胀为重量级锁的过程会引入较大的性能开销,因为需要进行线程的阻塞和唤醒操作。然而,当锁竞争激烈时,使用重量级锁可以更好地处理多线程并发访问的安全性问题。

相关文章:

12.轻量级锁原理及其实战

文章目录 轻量级锁原理及其实战1.轻量级锁的核心原理2.轻量级锁的演示2.1.轻量级锁的演示代码2.2.结果分析 3.轻量级锁的分类3.1.普通自旋锁3.2.自适应自旋锁 4.轻量级锁的膨胀 轻量级锁原理及其实战 引入轻量级锁的主要目的是在多线程环境竞争不激烈的情况下&#xff0c; 通过…...

栈结构(c语言)

1.栈的概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&am…...

【C++】C/C++中新const用法:const成员

欢迎来到CILMY23的博客 本篇主题为&#xff1a; C/C中新const用法&#xff1a;const成员 个人主页&#xff1a;CILMY23-CSDN博客 系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux 感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞…...

武汉凯迪正大—钢管焊缝裂纹探伤仪

产品概述 武汉凯迪正大无损探伤仪是一种便携式工业无损探伤仪器&#xff0c; 能够快速便捷、无损伤、精确地进行工件内部多种缺陷&#xff08;裂纹、夹杂、气孔等&#xff09;的检测、定位、评估和诊断。既可以用于实验室&#xff0c;也可以用于工程现场。 设置简单&#xff0c…...

为什么 IP 地址通常以 192.168 开头?

在网络配置中&#xff0c;我们经常会遇到以 192.168 开头的 IP 地址&#xff0c;例如 192.168.0.1 或者 192.168.1.100。 这些地址通常用于局域网中&#xff0c;但为什么要选择以 192.168 开头呢&#xff1f; 本文将深入探讨这个问题&#xff0c;并解释其背后的原因和历史渊源…...

elementUi中的el-table合计行添加点击事件

elementUi 文档中&#xff0c;合计行并没有点击事件&#xff0c;这里自己实现了合计行的点击事件。 created() {this.propertyList [{ property: order, label: 序号 },{ property: deptName, label: 单位名称 },{ property: contentPublishQuantity, label: 文章数量 },{ pro…...

Zookeeper集群搭建的一些问题

问题描述一&#xff1a; Cannot open channel to 2 at election address /192.168.60.132:3888解决方案&#xff1a; 查看zookeeper配置文件zoo.cfg / zoo_sample.cfg中集群配置部分 server.1zoo1-net1:2888:3888|zoo1-net2:2889:3889 server.2zoo2-net1:2888:3888|zoo2-net2…...

【线性代数】俗说矩阵听课笔记

基础解系的概念 线性方程组的解 21行列式和矩阵秩Rank的等价刻画 子式 标准型 利用子式求解矩阵的rank 24零积秩不等式 齐次线性方程组的基础解系 rank的两个重要结论 &#xffe5;25伴随矩阵的rank 奇异矩阵&#xff1a;行列式0的矩阵 31线性相关&#xff0c;线性无关&#…...

物联网技术在数字化工厂中的应用,你知道多少?——青创智通

工业物联网解决方案-工业IOT-青创智通 物联网&#xff08;IoT&#xff09;技术在数字化工厂的应用正日益成为工业革命的重要推动力。随着科技的飞速发展&#xff0c;物联网技术不断革新&#xff0c;其在数字化工厂中的应用也呈现出愈发广泛和深入的态势。本文将详细探讨物联网…...

nacos开启登录开关启动报错“Unable to start embedded Tomcat”

nacos 版本&#xff1a;2.3.2 2.2.2版本之前的Nacos默认控制台&#xff0c;无论服务端是否开启鉴权&#xff0c;都会存在一个登录页&#xff1b;在之后的版本关闭了默认登录页面&#xff0c;无需登录直接进入控制台操作。在这里我们可以在官网可以看到相关介绍 而我现在所用的…...

Linux|了解如何使用 awk 内置变量

引言 当我们揭开 Awk 功能部分时&#xff0c;我们将介绍 Awk 中内置变量的概念。您可以在 Awk 中使用两种类型的变量&#xff1a;用户定义的变量和内置变量。 内置变量的值已经在 Awk 中定义&#xff0c;但我们也可以仔细更改这些值&#xff0c;内置变量包括&#xff1a; FILEN…...

代码随想录-算法训练营day29【回溯算法05:递增子序列、全排列】

代码随想录-035期-算法训练营【博客笔记汇总表】-CSDN博客 第七章 回溯算法part05* 491.递增子序列 * 46.全排列 * 47.全排列 II详细布置 491.递增子序列 本题和大家刚做过的 90.子集II 非常像&#xff0c;但又很不一样&#xff0c;很容易掉坑里。 https://programmercarl.com…...

704. 二分查找

Problem: 704. 二分查找 &#x1f437;我的leetcode主页 文章目录 题目分类思路什么是二分查找如何理解时间复杂度 解题方法Code 题目 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&a…...

php回车变br、php显示br

在 PHP 中&#xff0c;如果你想将回车符&#xff08;\n&#xff09;转换为 HTML 的 <br> 标签来实现换行显示&#xff0c;可以使用内置函数 nl2br()。这个函数会将文本中的换行符替换为 <br> 标签。以下是使用 nl2br() 函数的示例代码&#xff1a; <?php $tex…...

找最大数字-第12届蓝桥杯国赛Python真题解析

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第60讲。 找最大数字&#…...

蓝桥杯 算法提高 ADV-1170 阶乘测试 python AC

找规律题&#xff0c;遍历i中有几个m就加几&#xff0c;和m的多少次数有关 第一版&#x1f447; try:while True:n, m map(int, input().split())ll [i for i in range(1, n 1) if i % m 0]ans len(ll)M mwhile ll:lll []M * mfor i in ll:if i % M 0:lll.append(i)a…...

阿里巴巴杭州全球总部正式启用,创新“减碳大脑”科技减碳 | 最新快讯

来源&#xff1a;封面新闻 封面新闻记者付文超 5 月 10 日&#xff0c;记者获悉&#xff0c;位于未来科技城的阿里巴巴杭州全球总部新园区正式启用&#xff0c;这是阿里巴巴目前最大的综合性办公园区。从空中俯瞰&#xff0c;园区正中央呈现阿里标志性的笑脸 logo&#xff0c;这…...

蓝桥杯国赛练习题真题Java(矩阵计数)

题目描述 一个 NM 的方格矩阵&#xff0c;每一个方格中包含一个字符 O 或者字符 X。 要求矩阵中不存在连续一行 3 个 X 或者连续一列 3 个 X。 问这样的矩阵一共有多少种&#xff1f; 输入描述 输入一行包含两个整数 N,M (1≤N,M≤5)。 输出描述 输出一个整数代表答案。…...

概念解析 | ROC曲线:评估分类模型

注1:本文系"概念解析"系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:ROC曲线的含义和绘制 概念解析 | ROC曲线:评估分类模型 第一部分:通俗解释 在我们的日常生活中,经常会遇到需要做出判断和选择的情况。比如,当你收到一封邮件时…...

数据可视化训练第二天(对比Python与numpy中的ndarray的效率并且可视化表示)

绪论 千里之行始于足下&#xff1b;继续坚持 1.对比Python和numpy的性能 使用魔法指令%timeit进行对比 需求&#xff1a; 实现两个数组的加法数组 A 是 0 到 N-1 数字的平方数组 B 是 0 到 N-1 数字的立方 import numpy as np def numpy_sum(text_num):"""…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...

【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验

Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...