并发?听我对你“锁”

news2025/1/18 4:50:07

本文主要讲讲,在Java中关于锁的一些知识点,并介绍一下对锁进行的一些优化

  • 一、前言
    • 1.阻塞锁
    • 2.非阻塞锁
  • 二、Syncnized和锁的底层原理
  • 三、锁优化
    • 1.自旋锁
    • 2.锁消除
    • 3.锁粗化
    • 4.轻量级锁
    • 5.偏向锁
  • 四、锁升级过程
  • 五、其他锁和AQS

一、前言

本文主要讲解Java中的锁和syncnized的一些知识,从我踏入社会开始,所接触的人都明确给我表示了项目中不能用syncnized,用syncnized都是外行诸如此类的话,那么syncnized到底能不能用?我的回答是可以用的,因为从jdk5以后,HotSpot虚拟机开发团队在这个版本上花费了大量的资源去实现各种锁优化技术,如适应性自旋、锁消除、锁膨胀、轻量级锁、偏向锁等。

其实Java中的锁,大体上可以分为两种

1.阻塞锁

阻塞锁,也叫互斥锁,典型的就是syncnized、或者实现Lock接口的一些其他锁例如ReentrantLock,这些锁最明显的特征就是,当锁住之后,当一个线程持有锁,会阻塞其他所有线程,直到持有者释放锁,被阻塞的线程,就会进行内核态和用户态的切换(后面加入了自旋优化),非常消耗性能,所以互斥锁是一种悲观锁

2.非阻塞锁

非阻塞锁相反,它是一种乐观锁,JDK5以后,Java类库种支持CAS(Compare and Swap比较交换)操作来实现非互斥锁。
CAS指令需要有三个操作数,分别是内存位置(在Java中可以简单地理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和准备设置的新值(用B表示)。CAS指令执行时,当且仅当V符合A时,处理器才会用B更新V的值,否则它就不执行更新。但是,不管是否更新了V的值,都会返回V的旧值,上述的处理过程是一个原子操作,执行期间不会被其他线程中断。

二、Syncnized和锁的底层原理

在Java中,最基本的锁就是syncnized,这是一种块结构锁,synchronized关键字经过Javac编译之后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。这两个字节码指令都需要一个reference类型的参数来指明要锁定和解锁的对象。
根据《Java虚拟机规范》的要求,在执行monitorenter指令时,首先要去尝试获取对象的锁。如果
这个对象没被锁定,或者当前线程已经持有了那个对象的锁,就把锁的计数器的值增加一,而在执行
monitorexit指令时会将锁计数器的值减一。一旦计数器的值为零,锁随即就被释放了。如果获取对象
锁失败,那当前线程就应当被阻塞等待,直到请求锁定的对象被持有它的线程释放为止。
所以综上所诉可以得到syncnized的两个特性:

  • 可重入
  • 当持有锁后,无条件阻塞其他线程

三、锁优化

1.自旋锁

  1. 自旋锁和自适应自旋锁分在一起来说,这是jdk6默认开启的一个优化,前面我们讨论互斥同步的时候,提到了互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力。
  2. 那有没有一种方式不让线程被挂起呢?有,那就是自旋等待,当两个线程竞争同一个资源,没有竞争到的那个线程,就在原地自旋等待,等对面拿到用完之后释放锁,自己再去获得锁,不过这点有利有弊,如果对方线程占用锁的时间很短,那自旋的确能起到优化的作用,如果对面迟迟不释放锁,那线程的自旋就是浪费资源,所以自旋的次数很重要,默认值是十次,用户也可以使用参数-XX:PreBlockSpin来自行更改,如果自旋超过了这个次数,就会挂起线程。
  3. 而自适应自旋,就是系统自己去判断自旋次数多少,程序运行越久,对对象的监控就越准确,所以次数的设置就约恰当。

2.锁消除

锁消除是指虚拟机在进行即时编译的时候,发现一些同步代码,其中不会涉及到锁竞争,那么就会将锁消除掉。这其中的主要判断逻辑,就是逃逸分析,如果一段代码中,所有的数据都不会产生逃逸,那么这些数据就可以当做栈上的数据来对待,属于线程私有,加锁自然就无需进行。

 public static void main(String[] args) {
     int a = 1;
     int b = 1;
     int c;
     synchronized (TestClass.class) {
         c = a + b;
     }
     System.out.println(c);
 }

比如这块带代码,synchronized 里面的所有操作,都不会逃逸到外部,加锁自然就没有必要。
你问我什么叫做逃逸?看下面这个方法init:
方法里面,我们创建一个对象userInfo,但是这个对象return了,那么就称userinfo产生了对象的逃逸,它可以被外部程序或者代码调用或者引用到

public UserInfo init() {
    UserInfo userInfo = new UserInfo();
    return userInfo;
}

再比如:init方法中的对象userInfo,通过参数传递的方式到了test方法,那么就称userInfo发生了参数逃逸。

 public void  init() {
     UserInfo userInfo = new UserInfo();
     test(userInfo);
 }

 public  void test(UserInfo userInfo){
     System.out.println(userInfo);
 }

3.锁粗化

锁的粗化就是,当虚拟机查询到一段多次加锁的代码,可以扩大合并才一个锁的时候,会进行的优化操作,比如典型的StringBuffer里的append方法。

  StringBuffer stringBuffer = new StringBuffer();
  stringBuffer.append("1");
  stringBuffer.append("2");
  stringBuffer.append("3");

相信大家也不陌生,每个append方法都是syncnized修饰的,所以虚拟机会将次代码优化为下面这种:

 StringBuffer stringBuffer = new StringBuffer();
 //伪代码,去掉所有append方法修饰符syncnized,外面统一包裹一层syncnized,大家知道意思就行。
 synchronized (){
     stringBuffer.append("1");
     stringBuffer.append("2");
     stringBuffer.append("3");
 }

4.轻量级锁

轻量级锁也是jdk6以后加入的新机制,它的轻量级是相对于传统锁来说的,所以传统的锁也可以成为“重量级”。
要说到轻量级锁,需要我们知道对象的内存布局,HotSpot虚拟机的对象头分为两部分,一部分是运行时数据,如HashCode,分代年龄等,这部分数据在32位机器上会占用32bit,官方称它为“Mark Word”。另一部分存放的指向方法区内存数据的指针,如果是数组还会存个长度。
声明:以下图示内容引用周志明老师的《深入理解java虚拟机第三版》。
例如在32位的HotSpot虚拟机中,对象未被锁定的状态下,Mark Word的32个比特空间里的25个比特将用于存储对象哈希码,4个比特用于存储对象分代年龄,2个比特用于存储锁标志位,还有1个比特固定为0(这表示未进入偏向模式)。
在这里插入图片描述
轻量级锁的工作过程:

  1. 在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,这时候线程堆栈与对象头的状态如图所示。
    在这里插入图片描述
  2. 然后,虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功了,即代表该线程拥有了这个对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后两个比特)将转变为“00”,表示此对象处于轻量级锁定状态。这时候线程堆栈与对象头的状态如图所示。
  3. 如果这个更新操作失败了,那就意味着至少存在一条线程与当前线程竞争获取该对象的锁。虚拟
    机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那直接进入同步块继续执行就可以了,否则就说明这个锁对象已经被其他线程抢占了。如果出现两条以上的线程争用同一个锁的情况,那轻量级锁就不再有效,必须要膨胀为重量级锁,锁标志的状态值变为“10”,此时Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也必须进入阻塞状态。
    在这里插入图片描述
  4. 上面描述的是轻量级锁的加锁过程,它的解锁过程也同样是通过CAS操作来进行的,如果对象的
    Mark Word仍然指向线程的锁记录,那就用CAS操作把对象当前的Mark Word和线程中复制的DisplacedMark Word替换回来。假如能够成功替换,那整个同步过程就顺利完成了;如果替换失败,则说明有其他线程尝试过获取该锁,就要在释放锁的同时,唤醒被挂起的线程。轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”这一经验法则。如果没有竞争,轻量级锁便通过CAS操作成功避免了使用互斥量的开销;但如果确实存在锁竞争,除了互斥量的本身开销外,还额外发生了CAS操作的开销。因此在有竞争的情况下,
    轻量级锁反而会比传统的重量级锁更慢。

5.偏向锁

偏向锁也是JDK 6中引入的一项锁优化措施,它的目的是消除数据在无竞争情况下的同步原语,
进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互
斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不去做了。

  1. 定义:偏向锁,会偏向于第一个获取它的线程,如果这个锁没有被其他线程获取,那么持有锁的线程永远不需要进行同步,一旦出现另一个线程尝试获取这个锁,偏向模式立即结束。
    在这里插入图片描述
  2. 由图可见,偏向锁会在Mark Word里面存储偏向的线程ID,会占用HashCode的位置,所以,当一个对象已经计算过一致性哈希码后,它就再也无法进入偏向锁状态了;而当一个对象当前正处于偏向锁状态,又收到需要计算其一致性哈希码请求时,它的偏向状态会被立即撤销,并且锁会膨胀为重量级锁。
  3. 偏向锁可以提高带有同步但无竞争的程序性能,但它同样是一个带有效益权衡性质的优化,也就是说它并非总是对程序运行有利。如果程序中大多数的锁都总是被多个不同的线程访问,那偏向模式就是多余的。在具体问题具体分析的前提下,有时候使用参数-XX:-UseBiasedLocking来禁止偏向锁优化反而可以提升性能。其实现在大部分编码情况下,偏向锁都很难成立,所以最新的jdk版本默认关闭了偏向锁。

四、锁升级过程

当一个锁,被一个线程第一次获取,那么这个锁就是一个偏向锁,如果这时候有第二个线程来竞争锁,那么锁就会升级为轻量级锁,在轻量级锁的时候,未持有锁的线程会自旋等待,如果自旋次数结束还没有获取到锁,就会升级为重量级锁。

五、其他锁和AQS

AQS是AbstractQueuedSynchronizer的缩写,它提供了一个FIFO队列,可以看成是一个实现同步锁的核心组件。AQS是一个抽象类,主要通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取和释放的方法来提供自定义的同步组件,AQS可以看做Lock接口下一整套结构的基石。
AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,如果当前线程竞争失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。

在这里插入图片描述
AQS队列内部维护的是一个FIFO的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的范文前驱和后继节点。每个Node其实是由线程封装,当线程争抢锁失败后会封装成Node加入到AQS队列中。

我们以最常见的ReentrantLock读写锁来简单看一下,ReentrantLock的tryLock,里面的实际实现是内部内的Sync,Sync继承AQS。

 public boolean tryLock() {
     return sync.nonfairTryAcquire(1);
 }

部分Sync源码

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //获取AQS的stae变量
        int c = getState();
        if (c == 0) {
        //CAS变动上锁
            if (compareAndSetState(0, acquires)) {
            //成功则上锁
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

ReentrantLock的tryLock默认的是非公平锁,用公平锁会极大降低锁的性能,慎用。

  • 公平锁:线程获取锁的顺序先来先得。
  • 非公平锁:线程获取锁跟顺序无关

其中关键就是getState()这个方法,AQS内部定义一个volatile 修饰的状态state,由它通过CAS变动的方式来控制线程的状态。

private volatile int state;

上面就是本文所有内容了,本人能力有限,有不对的或者需要补充的欢迎大家指出,也希望大家看完不要吝啬小指头,帮忙点个关注或者赞。

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

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

相关文章

【机器人学】7-2.六自由度机器人自干涉检测-计算圆柱体的上下圆心坐标【附MATLAB代码】

目录 前言 机械臂几何参数 机器等效圆柱体坐标确定 MATLAB代码 前言 上一章介绍了机器人自干涉检测的总体算法&#xff0c;提出了算法的三个核心&#xff1a; 一 根据机械臂的几何数据以及DH参数&#xff0c;确定机械臂等效的圆柱体的上下圆心坐标。 二 将一个圆柱体旋转到…

nginx部署vue前端打包项目

一、nginx安装 安装说明见&#xff1a;Nginx使用命令安装说明-CSDN博客 二、打包文件上传部署 将vue打包后的静态文件进行上传&#xff0c;打包后的目录如下&#xff1a; 将dist文件夹进行压缩为dist.zip&#xff0c;并将该目录打包上传至服务器的nginx目录&#xff1a; /usr…

新课程杂志社《新课程》杂志社2024年第19期目录查阅

教育前沿_新时代教育 享中华传统节日&#xff0c;传中华传统文化——传统节日综合性学习活动设计 姜秀芝; 1-3 中华优秀传统文化在小学语文阅读教学中的渗透——以综合性学习“中华传统节日”的教学为例 张小红; 4-7 开卷有益 在思考中探索&#xff0c;在探索中成长…

Qt 0820作业

一、思维导图 二、闹钟 头文件代码 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime> //时间类 #include <QTimer> //时间事件类 #include <QTimerEvent> //定时器事件类 #include <QMouseEvent> //鼠标…

无人机之故障排除篇

一、识别故障 掌握基本的无人机系统知识&#xff0c;遵循“先易后难、先外后内、先软件后硬件”的原则进行故障识别。一旦发现故障&#xff0c;立即停止飞行&#xff0c;避免进一步损坏。 二、机械部件维修 对于机身裂痕、螺旋桨损坏等情况&#xff0c;根据损坏程度更换相应部…

Tomcat目录详解

版本&#xff1a;Tomcat&#xff08;7.0.100&#xff09;&#xff08;linux版&#xff09; &#xff08;官网&#xff1a;Apache Tomcat - Welcome!&#xff09; 1.Tomcat是什么。 Tomcat是一个免费的开放源代码的Web 应用服务器&#xff0c;属于轻量级应用服务器。是Apache…

如何提高研发效能?思码逸 信通院给你答案

在市场竞争加剧的背景下&#xff0c;提高研发效能不仅是技术优化的需要&#xff0c;更是企业生存与发展的战略要求。在 2024 年伊始&#xff0c;北京思码逸科技有限公司&#xff08;简称“思码逸”&#xff09;携手合作伙伴启动了 DevData 2024 研发效能基准调研&#xff0c;并…

以简单的例子从头开始建spring boot web多模块项目(四)-多模块工具类

目的是为了验证主工程调用工具工程。 1、新建模块&#xff0c;名称为WebTool 同样为Maven Archetype&#xff0c;类型为 org.apache.maven.archetypes:maven-archetype-quickstart 2、修改pom.xml 增加spring-boot-starter的依赖。 <dependency><groupId>org.spri…

CISAW认证考试的时间是多久

CISAW&#xff0c;即中国信息安全保障人员&#xff0c;是中国信息安全认证与审查中心进行权威认证的缩写。它是全国范围内最为权威、最高端的信息安全认证之一。作为信息安全领域的重要认证&#xff0c;对于从事网络安全工作的人员来说具有极其重要的意义。因此&#xff0c;备考…

regeorg搭建socket隧道

regeorg搭建socket隧道 工具安装 下载地址 https://github.com/sensepost/reGeorg环境配置 说明 reGeorg提供了PHP、ASPX、JSP脚本&#xff0c;直接访问显示“Georg says, ‘All seems fine’”&#xff0c;表示脚本运行正常。# 攻击过程 vps:192.168.110.131 web服务器&a…

游戏内音乐盒、游戏内实时翻译外国队友语音的实现思路

奈何自己不能精通多个语言&#xff0c;在外服游戏的时候经常遇到老外叽里咕噜说一堆话&#xff0c;不知道讲些什么&#xff0c;可能有俄语、法语等&#xff0c;这时候有一个可以在游戏内实时翻译语言的工具就好了。 在本文中&#xff0c;我们将探讨如何提取游戏内的音频、队友…

Unity动画模块 之 3D模型导入基础设置 Rig页签

​本文仅作笔记学习和分享&#xff0c;不用做任何商业用途本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​​ 1.Rig页签 Rig 选项卡 - Unity 手册&#xff0c;rig是设置骨骼与替身系统的&#xff0c;工作流程如下 Avatar是什么…

OpenAI 将向企业开放 GPT-4o 模型定制版

OpenAI 最近发布了一项新功能&#xff0c;使企业客户可以通过微调技术定制 GPT-4o 模型&#xff0c;从而应对日益激烈的人工智能竞争&#xff0c;并展示其投资回报。这一新功能的推出使得企业能够使用自己的数据对 GPT-4o 模型进行个性化调整&#xff0c;以满足他们的特定需求和…

学习设置echarts 折线图使用相关参数的方法整理

学习设置echarts 折线图使用相关参数的方法整理 折线图堆叠设置为不堆叠的方法 折线图堆叠设置为不堆叠的方法 官网是这样的&#xff0c;但是不需要这种堆叠形式的如下图&#xff1a; 第2条数据值 第1条数据值 第2条数据值 第3条数据值 第2条数据值 第3条数据值 需要改成…

通讯专题-RS232

1 概述 RS-232是一种点对点通信协议&#xff0c;这意味着每个数据信号沿一根导线传输&#xff08;差分信号使用两根导线传输一个数据信号&#xff09;&#xff0c;RS-232为全双工方式运行&#xff08;总线可同时发送和接收数据&#xff09;。 根据新修订的标准为容性负载为2500…

Servlet技术介绍与实践

Servlet技术是Java Web开发的核心组件之一&#xff0c;它提供了一种在Web服务器上执行Java代码的机制。自Servlet API首次发布以来&#xff0c;它已成为构建动态Web应用程序的基础。本文将深入探讨Servlet的工作原理、生命周期、关键特性&#xff0c;并通过示例代码展示如何在实…

精通推荐算法28:行为序列建模之DSIN— 基于Session建模用户行为序列

1 行为序列建模总体架构 2 DSIN背景 阿里巴巴研究人员发现&#xff0c;用户行为序列是基于Session的。Session内兴趣相似且集中&#xff0c;Session间则兴趣差异较大。Session按照时间间隔来划分&#xff0c;比如 30分钟。如图5-11所示为真实场景下的多个用户行为Session。 图…

【NI国产替代】NI‑9235四分之一桥应变计,8通道C系列应变/桥输入模块

10 kS/s/ch&#xff0c;120 Ω四分之一桥应变计&#xff0c;8通道C系列应变/桥输入模块 NI‑9235可同步测量所有通道的动态应变&#xff0c;从而实现了高速同步测量。 该功能对于需要在特定时刻对多个通道进行比较的应用&#xff08;例如冲击测试&#xff09;非常重要。\n\nNI…

TCP协议段中的六个标志位

目录 ACK SYN RST FIN PSH URG TCP报文格式中的六个标志位由6个比特构成&#xff0c;在通信双方基于TCP协议互相发送报文数据时可以通过报头中标志位来区别对方发送的报文数据的请示。 ACK 确认号是否有效。 接收端对所收到的报文进行检查&#xff0c;若未发现错误&…

在控件graphicsView中实现绘图功能

文章目录 基础夯实&#xff1a;效果展示&#xff1a;一、目标&#xff1a;二、遇到的问题三、实例代码customgraphicsview.hcustomgraphicsview.cppmainwindow.hmainwindow.cppmian.cpp 基础夯实&#xff1a; 在Qt框架中&#xff0c;QGraphicsView 是一个非常强大的控件&#…