广告
返回顶部
首页 > 资讯 > 前端开发 > html >StampedLock怎么用
  • 444
分享到

StampedLock怎么用

2024-04-02 19:04:59 444人浏览 泡泡鱼
摘要

这篇文章主要介绍StampedLock怎么用,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!面对临界区资源管理的问题,大体上有2套思路:第一就是使用悲观的策略,悲观者这样认为:在每一次

这篇文章主要介绍StampedLock怎么用,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

面对临界区资源管理的问题,大体上有2套思路:

第一就是使用悲观的策略,悲观者这样认为:在每一次访问临界区的共享变量,总是有人会和我冲突,因此,每次访问我必须先住整个对象,完成访问后再解锁。

而与之相反的乐天派却认为,虽然临界区的共享变量会冲突,但是冲突应该是小概率事件,大部分情况下,应该不会发生,所以,我可以先访问了再说,如果等我用完了数据还没人冲突,那么我的操作就是成功;如果我使用完成后,发现有人冲突,那么我要么再重试一次,要么切换为悲观的策略。

 StampedLock怎么用

从这里不难看到,重入锁以及synchronized  是一种典型的悲观策略。聪明的你一定也猜到了,StampedLock就是提供了一种乐观锁的工具,因此,它是对重入锁的一个重要的补充。

StampedLock的基本使用

在StampedLock的文档中就提供了一个非常好的例子,让我们可以很快的理解StampedLock的使用。下面让我看一下这个例子,有关它的说明,都写在注释中了。

 StampedLock怎么用

这里再说明一下validate()方法的含义,函数签名长这样:

public boolean validate(long stamp)

它的接受参数是上次锁操作返回的邮戳,如果在调用validate()之前,这个锁没有写锁申请过,那就返回true,这也表示锁保护的共享数据并没有被修改,因此之前的读取操作是肯定能保证数据完整性和一致性的。

反之,如果锁在validate()之前有写锁申请成功过,那就表示,之前的数据读取和写操作冲突了,程序需要进行重试,或者升级为悲观锁。

和重入锁的比较

从上面的例子其实不难看到,就编程复杂度来说,StampedLock其实是要比重入锁复杂的多,代码也没有以前那么简洁了。

那么,我们为什么还要使用它呢?

最本质的原因,就是为了提升性能!一般来说,这种乐观锁的性能要比普通的重入锁快几倍,而且随着线程数量的不断增加,性能的差距会越来越大。

简而言之,在大量并发的场景中StampedLock的性能是碾压重入锁和读写锁的。

但毕竟,世界上没有十全十美的东西,StampedLock也并非全能,它的缺点如下:

编码比较麻烦,如果使用乐观读,那么冲突的场景要应用自己处理

它是不可重入的,如果一不小心在同一个线程中调用了两次,那么你的世界就清净了。。。。。

它不支持wait/notify机制

如果以上3点对你来说都不是问题,那么我相信StampedLock应该成为你的首选。

内部数据结构

为了帮助大家更好的理解StampedLock,这里再简单给大家介绍一下它的内部实现和数据结构。

在StampedLock中,有一个队列,里面存放着等待在锁上的线程。该队列是一个链表,链表中的元素是一个叫做Wnode的对象:

StampedLock怎么用

当队列中有若干个线程等待时,整个队列可能看起来像这样的:

StampedLock怎么用  

除了这个等待队列,StampedLock中另外一个特别重要的字段就是long state,  这是一个64位的整数,StampedLock对它的使用是非常巧妙的。

state 的初始值是:

private static final int LG_READERS = 7; private static final long WBIT  = 1L << LG_READERS; private static final long ORIGIN = WBIT << 1;

也就是 ...0001 0000 0000  (前面的0太多了,不写了,凑足64个吧~),为什么这里不用0做初始值呢?因为0有特殊的含义,为了避免冲突,所以选择了一个非零的数字。

如果有写锁占用,那么就让第7位设置为1 ...0001 1000 0000,也就是加上WBIT。

每次释放写锁,就加1,但不是state直接加,而是去掉最后一个字节,只使用前面的7个字节做统计。因此,释放写锁后,state就变成了:...0010  0000 0000, 再加一次锁,又变成:...0010 1000 0000,以此类推。

这里为什么要记录写锁释放的次数呢?

这是因为整个state  的状态判断都是基于CAS操作的。而普通的CAS操作可能会遇到ABA的问题,如果不记录次数,那么当写锁释放掉,申请到,再释放掉时,我们将无法判断数据是否被写过。而这里记录了释放的次数,因此出现"释放->申请->释放"的时候,CAS操作就可以检查到数据的变化,从而判断写操作已经有发生,作为一个乐观锁来说,就可以准确判断冲突已经产生,剩下的就是交给应用来解决冲突即可。因此,这里记录释放锁的次数,是为了精确地监控线程冲突。

而state剩下的那一个字节的其中7位,用来记录读锁的线程数量,由于只有7位,因此只能记录可怜的126个,看下面代码中的RFULL,就是读线程满载的数量。超过了怎么办呢,多余的部分就记录在readerOverflow字段中。

private static final long WBIT  = 1L << LG_READERS; private static final long RBITS = WBIT - 1L; private static final long RFULL = RBITS - 1L; private transient int readerOverflow;

总结一下,state变量的结构如下:

 StampedLock怎么用

写锁的申请和释放

在了解了StampedLock的内部数据结构之后,让我们再来看一下有关写锁的申请和释放吧!首先是写锁的申请:

public long writeLock() {     long s, next;       return ((((s = state) & ABITS) == 0L &&  //有没有读写锁被占用,如果没有,就设置上写锁标记              U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?             //如果写锁占用成功范围next,如果失败就进入acquireWrite()进行锁的占用。             next : acquireWrite(false, 0L)); }

如果CAS设置state失败,表示写锁申请失败,这时,会调用acquireWrite()进行申请或者等待。acquireWrite()大体做了下面几件事情:

1.入队

  • 如果头结点等于尾结点wtail == whead, 表示快轮到我了,所以进行自旋等待,抢到就结束了

  • 如果wtail==null ,说明队列都没初始化,就初始化一下队列

  • 如果队列中有其他等待结点,那么只能老老实实入队等待了

2.阻塞并等待

  • 如果头结点等于前置结点(h = whead) == p), 那说明也快轮到我了,不断进行自旋等待争抢

  • 否则唤醒头结点中的读线程

  • 如果抢占不到锁,那么就park()当前线程

StampedLock怎么用  

简单地说,acquireWrite()函数就是用来争抢锁的,它的返回值就是代表当前锁状态的邮戳,同时,为了提高锁的性能,acquireWrite()使用大量的自旋重试,因此,它的代码看起来有点晦涩难懂。

写锁的释放如下所示,unlockWrite()的传入参数是申请锁时得到的邮戳:

public void unlockWrite(long stamp) {     WNode h;     //检查锁的状态是否正常     if (state != stamp || (stamp & WBIT) == 0L)         throw new IllegalMonitorStateException();     // 设置state中标志位为0,同时也起到了增加释放锁次数的作用     state = (stamp += WBIT) == 0L ? ORIGIN : stamp;     // 头结点不为空,尝试唤醒后续的线程     if ((h = whead) != null && h.status != 0)         //唤醒(unpark)后续的一个线程         release(h); }

读锁的申请和释放

获取读锁的代码如下:

public long readLock() {     long s = state, next;       //如果队列中没有写锁,并且读线程个数没有超过126,直接获得锁,并且读线程数量加1     return ((whead == wtail && (s & ABITS) < RFULL &&              U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?             //如果争抢失败,进入acquireRead()争抢或者等待             next : acquireRead(false, 0L)); }

acquireRead()的实现相当复杂,大体上分为这么几步:

StampedLock怎么用

总之,就是自旋,自旋再自旋,通过不断的自旋来尽可能避免线程被真的挂起,只有当自旋充分失败后,才会真正让线程去等待。

下面是释放读锁的过程:

 StampedLock怎么用

StampedLock悲观读占满CPU的问题

StampedLock固然是个好东西,但是由于它特别复杂,难免也会出现一些小问题。下面这个例子,就演示了StampedLock悲观锁疯狂占用CPU的问题:

public class StampedLockTest {     public static void main(String[] args) throws InterruptedException {         final StampedLock lock = new StampedLock();         Thread t1 = new Thread(() -> {             // 获取写锁             lock.writeLock();             // 模拟程序阻塞等待其他资源             LockSupport.park();         });         t1.start();         // 保证t1获取写锁         Thread.sleep(100);         Thread t2 = new Thread(() -> {             // 阻塞在悲观读锁             lock.readLock();         });         t2.start();         // 保证t2阻塞在读锁         Thread.sleep(100);         // 中断线程t2,会导致线程t2所在CPU飙升         t2.interrupt();         t2.join();     } }

上述代码中,在中断t2后,t2的CPU占用率就会沾满100%。而这时候,t2正阻塞在readLock()函数上,换言之,在受到中断后,StampedLock的读锁有可能会占满CPU。这是什么原因呢?机制的小傻瓜一定想到了,这是因为StampedLock内太多的自旋引起的!没错,你的猜测是正确的。

具体原因如下:

如果没有中断,那么阻塞在readLock()上的线程在经过几次自旋后,会进入park()等待,一旦进入park()等待,就不会占用CPU了。但是park()这个函数有一个特点,就是一旦线程被中断,park()就会立即返回,返回还不算,它也不给你抛点异常啥的,那这就尴尬了。本来呢,你是想在锁准备好的时候,unpark()的线程的,但是现在锁没好,你直接中断了,park()也返回了,但是,毕竟锁没好,所以就又去自旋了。

转着转着,又转到了park()函数,但悲催的是,线程的中断标记一直打开着,park()就阻塞不住了,于是乎,下一个自旋又开始了,没完没了的自旋停不下来了,所以CPU就爆满了。

要解决这个问题,本质上需要在StampedLock内部,在park()返回时,需要判断中断标记为,并作出正确的处理,比如,退出,抛异常,或者把中断位给清理一下,都可以解决问题。

但很不幸,至少在jdk8里,还没有这样的处理。因此就出现了上面的,中断readLock()后,CPU爆满的问题。请大家一定要注意。

以上是“StampedLock怎么用”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网html频道!

--结束END--

本文标题: StampedLock怎么用

本文链接: https://www.lsjlt.com/news/80297.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • StampedLock怎么用
    这篇文章主要介绍StampedLock怎么用,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!面对临界区资源管理的问题,大体上有2套思路:第一就是使用悲观的策略,悲观者这样认为:在每一次...
    99+
    2022-10-19
  • Java并发编程之StampedLock锁怎么应用
    本篇内容介绍了“Java并发编程之StampedLock锁怎么应用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!StampedLock:St...
    99+
    2023-06-30
  • Java利用StampedLock实现读写锁的方法详解
    目录概述StampedLock介绍演示例子性能对比总结概述 想到读写锁,大家第一时间想到的可能是ReentrantReadWriteLock。实际上,在jdk8以后,java提供了一...
    99+
    2022-11-13
    Java StampedLock读写锁 Java StampedLock Java 读写锁
  • 如何使用高性能解决线程饥饿的利器StampedLock
    本篇内容介绍了“如何使用高性能解决线程饥饿的利器StampedLock”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够...
    99+
    2022-10-19
  • chkdsk怎么用
    chkdsk是一个Windows命令行工具,用于检查和修复文件系统错误。以下是使用chkdsk命令的一些常见用法:1. 打开命令提示...
    99+
    2023-09-15
    chkdsk
  • mac怎么用
    这篇文章将为大家详细讲解有关mac怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。篇一、Mac的常用快捷键在一些 Apple 自己的键盘上,通常顶行中会有特殊按键,有音量图标、显示屏亮度图标和其他功能...
    99+
    2023-06-05
  • sitemesh怎么用
    这篇文章给大家分享的是有关sitemesh怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一,基本概念1,Sitemesh是一种页面装饰技术 : 1  :它通过过滤器(filte...
    99+
    2023-06-08
  • sed怎么用
    这篇文章给大家分享的是有关sed怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。例如,在多个系统、应用程序安装之后,我们往往需要对很多配置文件进行修改,用vi编辑器意味着耗费时间、重复劳动,而sed就可将我们...
    99+
    2023-06-09
  • Vim怎么用
    这篇文章主要为大家展示了“Vim怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Vim怎么用”这篇文章吧。安装在 Ubuntu 中可以使用如下命令来安装 Vim:sudo apt-get i...
    99+
    2023-06-13
  • memtest怎么用
    要使用Memtest进行内存测试,您需要按照以下步骤进行操作:1. 下载Memtest软件:您可以从Memtest官方网站(http...
    99+
    2023-09-17
    memtest
  • chatGPT怎么用
    chatGPT使用的方法:1、登录chatgpt官网;2、按照步骤完成账号注册;3、注册完成后登陆,按自身需求调节白天黑夜模式;4、...
    99+
    2023-02-08
    chatGPT
  • Postman怎么用
    这篇文章主要介绍Postman怎么用,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、什么是 Postman(前世今生)Postman 诞生于 2013 年,一开始只是 Abhinav Asthana 着手于解决 A...
    99+
    2023-06-21
  • Ncat怎么用
    这篇文章主要为大家展示了“Ncat怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Ncat怎么用”这篇文章吧。Ncat 是用于在网络上读取,写入,重定向和加密数据的通用命令行工具。它旨在成为...
    99+
    2023-06-27
  • MongoDB怎么用
    小编给大家分享一下MongoDB怎么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中...
    99+
    2023-06-27
  • Entity怎么用
    这篇文章主要介绍了Entity怎么用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Entity怎么用文章都会有所收获,下面我们一起来看看吧。Entity是基于JPA规范。更详细的技术细节请参考JPA或Hiber...
    99+
    2023-06-26
  • crontab怎么用
    小编给大家分享一下crontab怎么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Linux crontab是用来定期执行程序的命令,当安装完成操作系统之后,默...
    99+
    2023-06-27
  • 怎么用Dapper
    本文小编为大家详细介绍“怎么用Dapper”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么用Dapper”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。dapper除了支持基础的CURD、存储过程以外,还支持操...
    99+
    2023-06-29
  • megui怎么用
    Megui是一个视频转码软件,用于将视频文件转换为其他格式。以下是使用Megui的一般步骤:1. 下载和安装Megui软件:可以在M...
    99+
    2023-09-17
    megui
  • java怎么用
      本次来跟大家聊聊关于Java安装后如何使用的问题。我第一次接触Java时也是一头雾水,本以为安装了Java这款软件就能直接开始Java编程,其实不然,还要搭建小小的开发条件。推荐教程:Java入门视频教程1、首先安装jdk  这里先说明...
    99+
    2018-01-07
    java教程 java
  • 怎么用redis
    这篇文章运用简单易懂的例子给大家介绍怎么用redis,代码非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。安装redis后,在命令行输入“redis-cli"回车输入”auth pa...
    99+
    2022-10-18
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作