深入了解Lock同步锁的优化

news/2024/5/18 15:21:58/文章来源:https://blog.csdn.net/qq_35030548/article/details/130313379

大家好,我是易安。

今天我们来简单谈谈在JDK1.5之后,Java提供的Lock同步锁。

相对于需要JVM隐式获取和释放锁的Synchronized同步锁,Lock同步锁(以下简称Lock锁)需要的是显示获取和释放锁,这就为获取和释放锁提供了更多的灵活性。Lock锁的基本操作是通过乐观锁来实现的,但由于Lock锁也会在阻塞时被挂起,因此它依然属于悲观锁。我们可以通过一张图来简单对比下两个同步锁,了解下各自的特点:

alt

从性能方面上来说,在并发量不高、竞争不激烈的情况下,Synchronized同步锁由于具有分级锁的优势,性能上与Lock锁差不多;但在高负载、高并发的情况下,Synchronized同步锁由于竞争激烈会升级到重量级锁,性能则没有Lock锁稳定。

我们可以通过一组简单的性能测试,直观地对比下两种锁的性能,结果见下方,代码可以在 Github 上下载查看。

alt

通过以上数据,我们可以发现:Lock锁的性能相对来说更加稳定。

Lock锁的实现原理

Lock锁是基于Java实现的锁,Lock是一个接口类,常用的实现类有ReentrantLock、ReentrantReadWriteLock(RRW),它们都是依赖AbstractQueuedSynchronizer(AQS)类实现的。

AQS类结构中包含一个基于链表实现的等待队列(CLH队列),用于存储所有阻塞的线程,AQS中还有一个state变量,该变量对ReentrantLock来说表示加锁状态。

该队列的操作均通过CAS操作实现,我们可以通过一张图来看下整个获取锁的流程。

alt

锁分离优化Lock同步锁

虽然Lock锁的性能稳定,但也并不是所有的场景下都默认使用ReentrantLock独占锁来实现线程同步。

我们知道,对于同一份数据进行读写,如果一个线程在读数据,而另一个线程在写数据,那么读到的数据和最终的数据就会不一致;如果一个线程在写数据,而另一个线程也在写数据,那么线程前后看到的数据也会不一致。这个时候我们可以在读写方法中加入互斥锁,来保证任何时候只能有一个线程进行读或写操作。

在大部分业务场景中,读业务操作要远远大于写业务操作。而在多线程编程中,读操作并不会修改共享资源的数据,如果多个线程仅仅是读取共享资源,那么这种情况下其实没有必要对资源进行加锁。如果使用互斥锁,反倒会影响业务的并发性能,那么在这种场景下,有没有什么办法可以优化下锁的实现方式呢?

1.读写锁ReentrantReadWriteLock

针对这种读多写少的场景,Java提供了另外一个实现Lock接口的读写锁RRW。我们已知ReentrantLock是一个独占锁,同一时间只允许一个线程访问,而RRW允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。读写锁内部维护了两个锁,一个是用于读操作的ReadLock,一个是用于写操作的WriteLock。

那读写锁又是如何实现锁分离来保证共享资源的原子性呢?

RRW也是基于AQS实现的,它的自定义同步器(继承AQS)需要在同步状态state上维护多个读线程和一个写线程的状态,该状态的设计成为实现读写锁的关键。RRW很好地使用了高低位,来实现一个整型控制两种状态的功能,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写。

一个线程尝试获取写锁时, 会先判断同步状态state是否为0。如果state等于0,说明暂时没有其它线程获取锁;如果state不等于0,则说明有其它线程获取了锁。

此时再判断同步状态state的低16位(w)是否为0,如果w为0,则说明其它线程获取了读锁,此时进入CLH队列进行阻塞等待;如果w不为0,则说明其它线程获取了写锁,此时要判断获取了写锁的是不是当前线程,若不是就进入CLH队列进行阻塞等待;若是,就应该判断当前线程获取写锁是否超过了最大次数,若超过,抛异常,反之更新同步状态。

alt

一个线程尝试获取读锁时, 同样会先判断同步状态state是否为0。如果state等于0,说明暂时没有其它线程获取锁,此时判断是否需要阻塞,如果需要阻塞,则进入CLH队列进行阻塞等待;如果不需要阻塞,则CAS更新同步状态为读状态。

如果state不等于0,会判断同步状态低16位,如果存在写锁,则获取读锁失败,进入CLH阻塞队列;反之,判断当前线程是否应该被阻塞,如果不应该阻塞则尝试CAS同步状态,获取成功更新同步锁为读状态。

alt

下面我们通过一个求平方的例子,来感受下RRW的实现,代码如下:

public class TestRTTLock {

 private double x, y;

 private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
 // 读锁
 private Lock readLock = lock.readLock();
 // 写锁
 private Lock writeLock = lock.writeLock();

 public double read() {
  //获取读锁
  readLock.lock();
  try {
   return Math.sqrt(x * x + y * y);
  } finally {
   //释放读锁
   readLock.unlock();
  }
 }

 public void move(double deltaX, double deltaY) {
  //获取写锁
  writeLock.lock();
  try {
   x += deltaX;
   y += deltaY;
  } finally {
   //释放写锁
   writeLock.unlock();
  }
 }

}

2.读写锁再优化之StampedLock

RRW被很好地应用在了读大于写的并发场景中,然而RRW在性能上还有可提升的空间。在读取很多、写入很少的情况下,RRW会使写入线程遭遇饥饿(Starvation)问题,也就是说写入线程会因迟迟无法竞争到锁而一直处于等待状态。

在JDK1.8中,Java提供了StampedLock类解决了这个问题。StampedLock不是基于AQS实现的,但实现的原理和AQS是一样的,都是基于队列和锁状态实现的。与RRW不一样的是,StampedLock控制锁有三种模式: 写、悲观读以及乐观读,并且StampedLock在获取锁时会返回一个票据stamp,获取的stamp除了在释放锁时需要校验,在乐观读模式下,stamp还会作为读取共享资源后的二次校验。

我们先通过一个官方的例子来了解下StampedLock是如何使用的,代码如下:

public class Point {
    private double x, y;
    private final StampedLock s1 = new StampedLock();

    void move(double deltaX, double deltaY) {
        //获取写锁
        long stamp = s1.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            //释放写锁
            s1.unlockWrite(stamp);
        }
    }

    double distanceFormOrigin() {
        //乐观读操作
        long stamp = s1.tryOptimisticRead();
        //拷贝变量
        double currentX = x, currentY = y;
        //判断读期间是否有写操作
        if (!s1.validate(stamp)) {
            //升级为悲观读
            stamp = s1.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                s1.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

我们可以发现:一个写线程获取写锁的过程中,首先是通过WriteLock获取一个票据stamp,WriteLock是一个独占锁,同时只有一个线程可以获取该锁,当一个线程获取该锁后,其它请求的线程必须等待,当没有线程持有读锁或者写锁的时候才可以获取到该锁。请求该锁成功后会返回一个stamp票据变量,用来表示该锁的版本,当释放该锁的时候,需要unlockWrite并传递参数stamp。

接下来就是一个读线程获取锁的过程。首先线程会通过乐观锁tryOptimisticRead操作获取票据stamp ,如果当前没有线程持有写锁,则返回一个非0的stamp版本信息。线程获取该stamp后,将会拷贝一份共享资源到方法栈,在这之前具体的操作都是基于方法栈的拷贝数据。

之后方法还需要调用validate,验证之前调用tryOptimisticRead返回的stamp在当前是否有其它线程持有了写锁,如果是,那么validate会返回0,升级为悲观锁;否则就可以使用该stamp版本的锁对数据进行操作。

相比于RRW,StampedLock获取读锁只是使用与或操作进行检验,不涉及CAS操作,即使第一次乐观锁获取失败,也会马上升级至悲观锁,这样就可以避免一直进行CAS操作带来的CPU占用性能的问题,因此StampedLock的效率更高。

总结

不管使用Synchronized同步锁还是Lock同步锁,只要存在锁竞争就会产生线程阻塞,从而导致线程之间的频繁切换,最终增加性能消耗。因此,如何降低锁竞争,就成为了优化锁的关键。

在Synchronized同步锁中,我们了解了可以通过减小锁粒度、减少锁占用时间来降低锁的竞争。我们知道可以利用Lock锁的灵活性,通过锁分离的方式来降低锁竞争。

Lock锁实现了读写锁分离来优化读大于写的场景,从普通的RRW实现到读锁和写锁,到StampedLock实现了乐观读锁、悲观读锁和写锁,都是为了降低锁的竞争,促使系统的并发性能达到最佳。

如果本文对你有帮助的话,欢迎点赞分享,这对我继续分享&创作优质文章非常重要。感谢 !

本文由 mdnice 多平台发布

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NX状态检测

输入 sudo -H pip install jetson-stats 如果提示没有pip,那么就输入 sudo apt-get install python-pip 之后输入 sudo jtop进行监测 用这个方法可以看到当前Jetpack的版本

Java核心技术 卷1-总结-13

Java核心技术 卷1-总结-13 具体的集合散列集树集队列与双端队列优先级队列 映射基本映射操作 具体的集合 散列集 链表和数组可以有序的存储元素。但是,如果想要查看某个指定的元素,却又忘记了它的位置,就需要访问所有元素,直到找…

MQTT 开放基准测试规范:全面评估你的 MQTT Broker 性能

引言 我们很高兴地宣布:由 EMQ 提供的 MQTT 开放基准测试规范现已正式发布! 该测试规范包含了实用的典型使用场景、一套衡量 Broker 性能的主要指标,以及一个模拟负载和收集测试结果的工具,可以帮助开发者评估 MQTT Broker 的可…

【后续】使用nvm替换nvmw作为nodejs的版本切换(亲测)

文接上篇,使用nvm替换nvmw作为nodejs的版本切换(亲测) 如图各种乱码,在vscoe中也是出现 真的好烦啊。看到提示的还是之前nvmw不能执行的各种报错。 XXX不是内部或外部命令,也不是可运行的程序或批处理文件。 决定&am…

基于matlab使用波束成形生成 802.11ad 波形

一、前言 本示例说明如何使用WLAN工具箱和相控阵系统工具箱对带有相控阵的IEEE 802.11ad DMG波形进行波束成形。 二、介绍 IEEE 802.11ad 定义了工作在 60 GHz 的定向千兆位 (DMG) 传输格式。为了克服在 60 GHz 下遇到的大路径损耗,IEEE 802.…

python字符串模糊匹配,并计算匹配分数

一、thefuzz thefuzz包以前叫fuzzywuzzy,0.19版本开始改名为thefuzz,github地址: GitHub - seatgeek/thefuzz: Fuzzy String Matching in Python 可以通过命令pip install thefuzz安装此包。用法还是比较简单的: from thefuzz import fuz…

【JAVA】easyExcel导出导入使用

EasyExcel是阿里巴巴开源插件之一,主要解决了poi框架使用复杂,sax解析模式不容易操作,数据量大起来容易OOM,解决了POI并发造成的报错。主要解决方式:通过解压文件的方式加载,一行一行地加载,并且…

春秋云境:CVE-2022-25099(文件上传造成RCE)

目录 一、题目 二、burp上传执行木马 一、题目 介绍: WBCE CMS v1.5.2 /language/install.php 文件存在漏洞,攻击者可精心构造文件上传造成RCE 进入题目: 网站正在建设中。。。 直接访问/admin吧: admin:123456 成功进入&…

视频直播网站开发的最佳实践

随着互联网技术的不断发展,视频直播成为了网络世界中的一股热潮。无论是企业还是个人,都可以通过搭建自己的视频直播网站来实现自己的目标。但是,对于很多企业来说,视频直播网站的开发是一项复杂的任务。因此,本文将介…

【微信小程序】详解behaviors,如何使用behaviors

一,behaviors 1.1什么是 behaviors? behaviors 是小程序中, 用于实现组件间代码共享的特性 ,类似于 Vue.js 中的 “mixins”。 1.2behaviors 的工作方式 每个 behavior 可以包含一组 属性、数据、生命周期函数和方法 。组件引…

E5EAA HENF105240R1将用于工业生产过程的测量、控制和管理

​E5EAA HENF105240R1将用于工业生产过程的测量、控制和管理 工业控制计算机是工业自动化控制系统的核心设备 工业控制计算机是工业自动化设备和信息产业基础设备的核心。传统意义上,将用于工业生产过程的测量、控制和管理的计算机统称为工业控制计算机,…

哈希表(底层结构剖析-- 上)

文章目录 哈希表底层结构剖析哈希概念哈希冲突哈希函数 哈希冲突解决办法闭散列( 线性探测 二次探测)开散列 哈希表闭散列方法的模拟实现基本框架有关哈希数据的类插入函数删除函数查找函数增加仿函数将所有数据类型转换为整型 哈希表开散列方法的模拟实现(增加仿函数版) 哈希…

FME教程:GIS建筑面转CAD格式JMD,还原房屋建筑结构、层数、地物样式,shp转CAD,GIS转dwg

GIS数据转CAD数据,是经常遇到的需求,但是CAD数据形式与GIS相差甚远,因此GIS转CAD后,要还原图形样式和地物属性便成为了一个难点。 今天介绍使用FME进行shp格式房屋面数据转dwg格式的JMD图层的方法。实现房屋的地物样式、结构、层…

el-table中el-select与span 通过点击按钮进行编辑与显示切换;js给数据中的对象添加属性

效果实现&#xff1a; 1.点击新增一行 表格插入空白行 2.当表格项有数据时显示模式 操作显示修改和删除 3.点击修改切换成编辑模式&#xff08;此处是el-select&#xff09; 代码部分 <div class"addBtn"><el-button type"primary" size"m…

某医院网络安全分析案例

背景 我们已将NetInside流量分析系统部署到某市医院的机房内&#xff0c;使用流量分析系统提供实时和历史原始流量。本次分析重点针对网络流量安全进行分析&#xff0c;以供安全取证、网络质量监测以及深层网络分析。 分析时间 报告分析时间范围为&#xff1a;2023-04-12 16…

实验07:子集和问题

1.实验目的&#xff1a; 深刻理解回溯法的基本思想&#xff0c;掌握回溯法解决问题的一般步骤&#xff0c;学会使用回溯法解决实际问题.运用所熟悉的编程工具&#xff0c;借助回溯法的思想求解子集和数的问题。 2.实验内容&#xff1a; 给定 n n n 个正整数 { x 1 , x 2 ,…

短视频app搭建的技术难点是什么?

近年来&#xff0c;短视频app的流行引起了广泛关注。越来越多的企业开始投入资源来开发短视频app&#xff0c;以满足用户的需求。然而&#xff0c;短视频app的开发过程中&#xff0c;存在许多技术难点需要解决。本文将深入分析短视频app搭建的技术难点。 短视频app的意义 随着…

MySQL_第12章_MySQL数据类型精讲

第12章_MySQL数据类型精讲 1. MySQL中的数据类型 类型 类型举例 整数类型 TINYINT 、 SMALLINT 、 MEDIUMINT 、 INT( 或 INTEGER) 、 BIGINT 浮点类型 FLOAT 、 DOUBLE 定点数类型 DECIMAL 位类型 BIT 日期时间类型 YEAR 、 TIME 、 DATE 、 DATETIME 、 TIMESTAMP 文…

RS485及脉冲信号拉线位移传感器自动判别和控制数据传输方向

RS485信号拉线位移传感器集线器具有端口故障告警功能&#xff0c;并能自动切断故障端口。RS485集线器短路开路保护设计能够保证连接的端口设备发生故障时&#xff0c;出现问题的端口将被隔离&#xff0c;确保其他网段设备正常工作。 2、RS485信号拉线位移传感器或者其他RS485总…

ChatGPT其实并不想让开发人员做这5件事情,但却已经被玩坏了

前言 ChatGPT已经火爆了快半年了吧&#xff0c;紧接着国内也开始推出了各种仿制品&#xff0c;我甚至一度怀疑&#xff0c;如果人家没有推出ChatGPT&#xff0c;这些仿制品会不会出现。而很多人也嗨皮得不行&#xff0c;利用各种方法开始科学上网&#xff0c;用ChatGPT做各种觉…