JUC高级-0625

news2024/11/24 4:31:50

13. AbstractQueuedSynchronized之AQS

13.1 前置知识

  1. 公平锁和非公平锁
  2. 可重入锁
  3. 自旋思想
  4. LockSupport
  5. 数据结构之双向链表
  6. 设计模式之模板设计模式

13.2 AQS入门级别理论知识

  1. AQS是什么?
    1. 字面意思:抽象的队列同步器,实现了通知唤醒的机制
    2. 源代码
      请添加图片描述
    3. 技术解释
      1. 是用来实现锁或者其它同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给"谁"的问题
      2. 整体就是一个抽象的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态
      3. 为实现阻塞锁和相关的同步器提供一个框架,它是依赖于先进先出的一个等待。依靠单个原子int值来表示状态,通过占用和释放方法,改变状态值
      4. 请添加图片描述
    4. AQS是JUC基石
      请添加图片描述
    5. 进一步理解锁和同步器的关系
      1. 锁,面向锁的使用者:定义了程序员和锁交互的使用层APl,隐藏了实现细节,你调用即可。
      2. 同步器,面向锁的实现者:Java并发大神DougLee,提出统一规范并简化了锁的实现,将其抽象出来屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知、唤醒机制等,是一切锁和同步组件实现的--------公共基础部分
    6. 能做什么?
      1. 加锁会导致阻塞:在阻塞就需要排队,实现排队必然需要队列
      2. 解释说明:
        1. 抢到资源的线程直接使用处理业务,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
        2. 既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
        3. 如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点==(Node)==,通过CAS、自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。
        4. 总结:AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改。
  2. 基本结构:
    请添加图片描述

13.3 AQS源码分析前置知识储备

  1. AQS本质:双向队列+state状态位,内部靠Node节点形成队列
  2. AQS内部体系架构
    请添加图片描述
  3. AQS自身:
    1. AQS的int变量volatile int state,也就是13.2.2图中的state
    2. AQS的CLH队列:也就是13.2.2图中的队列。三个科学家名字拼起来的。
    3. 属性:头尾指针外加state属性
      private transient volatile Node head;
      private transient volatile Node tail;
      private volatile int state;
      
    4. 小总结:
      1. 有阻塞就需要排队,实现排队必然需要队列
      2. state变量+CLH双端队列
  4. AQS内部类Node:
    //	共享
    static final Node SHARED = new Node();
    //	独占       
    static final Node EXCLUSIVE = null;
    // 线程被取消了        
    static final int CANCELLED =  1;	
    //	后继线程需要唤醒      
    static final int SIGNAL    = -1;
    //	等待condition唤醒        
    static final int CONDITION = -2;
    //	共享式同步状态获取将会无条件地传播下去       
    static final int PROPAGATE = -3;
    //	初始为0,状态是上面的几种
    volatile int waitStatus;		//	很重要
    //	前置节点
    volatile Node prev;
    //	后置节点
    volatile Node next;
    
    volatile Thread thread;
    
    等候区其它顾客(其它线程)的等待状态,队列中每个排队的个体就是一个 Node
  5. Node属性说明:
    请添加图片描述

13.4 AQS源码深度讲解和分析

此部分:AQS源码分析——以ReentrantLock为例

13.5 源码分析总结

请添加图片描述
请添加图片描述
请添加图片描述

14. ReentrantLock、 ReentrantReadWriteLock、 StampedLock讲解

  1. 本章主线:无锁——独占锁——读写锁——邮戳锁

14.1 简单聊聊ReentrantReadWriteLock

  1. 是什么
    1. 是ReadWriteLock的实现类
    2. 读写锁定义为:一个资源能够被多个國线程访间,或者被工个写线程访问,但是不能同时存在读写线程。
    3. 简单说,一锁多用;一体两面,读写互斥,读读共享
  2. 演变:无锁无序——加锁——读写锁演变
    1. 请添加图片描述
    2. 写线程饥饿问题
    3. 锁降级
  3. 读写锁意义和特点:
    1. 读写锁ReentrantReadWriteLock』并不是真正意义上的读写分离,它只允许读读共存,而读写和写写依然是互斥的,大多实际场景是“读/读”线程间并不存在互斥关系,只有"读/写"线程或"写/写"线程间的操作需要互斥的。因此引入ReentrantReadWriteLock。
    2. 一个ReentrantReadWriteLock同时只能存在一个写锁但是可以存在多个读锁,但不能同时存在写锁和读锁(切菜还是拍蒜选一个)。也即一个资源可以被多个读操作访问或一个写操作访问,但两者不能同时进行。
    3. 只有在读多写少情境之下,读写锁才具有较高的性能体现。
  4. 锁降级
    1. 锁降级:遵循获取写锁→再获取读锁→再释放写锁的次序,写锁能够降级成为读锁。如果一个线程占有了写锁,在不释放写锁的情况下,它还能占有读锁,即写锁降级为读锁。
    2. Java8 官网说明:
      重入还允许通过获取写入锁定,然后读取锁然后释放写锁从写锁到读取锁, 但是,从读锁定升级到写锁是不可能的
    3. 写锁的降级,降级成为了读锁
      1. 目的:锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性
      2. 如果同一个线程持有了写锁,在没有释放写锁的情况下,它还可以继续获得读锁。这就是写锁的降级,降级成为了读锁。
      3. 规则惯例,先获取写锁,然后获取读锁,再择放写锁的 次序。
      4. 如果释放了写锁,那么就完全转换为读锁。
      5. 请添加图片描述
    4. 锁降级特性:
      1. 公平性选择:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平
      2. 重进入:该锁支持重进人,以读写线程为例:读线程在获取了读锁之后,能够再次获取读锁。而写线程在获取了写锁之后能够再次获取写锁,同时也可以获取读锁
      3. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁
    5. 代码演示:见https://blog.csdn.net/lannister_awalys_pay/article/details/131078204 9.4
    6. 不可锁升级:
      请添加图片描述
    7. 小总结:
      1. 写锁和读锁是互斥的(这里的互斥是指线程间的互斥,当前线程可以获取到写锁又获取到读锁,但是获取到了读锁不能继续获取写锁),这是因为读写锁要保持写操作的可见性。因为,如果允许读锁在被获取的情况下对写锁的获取,那么正在运行的其他读线程无法感知到当前写线程的操作。
      2. 因此,分析读写锁ReentrantReadWriteLock,会发现它有个潜在的问题:读锁全完,写锁有望;写锁独占,读写全堵;如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,见前面Case《code演示LockDownGradingDemo》即ReadWriteLock读的过程中不允许写,只有等待线程都释放了读锁,当前线程才能获取写锁,也就是写入必须等待,这是一种悲观的读锁,o(╥﹏╥)o,人家还在读着那,你先别去写,省的数据乱。
      3. =========后续讲解StampedLock时再详细展开===========
        分析StampedLock(后面详细讲解),会发现它改进之处在于:读的过程中也允许获取写锁介入(相当牛B,读和写两个操作也让你“共享”(注意引号)),这样会导致我们读的数据就可能不一致!所以,需要额外的方法来判断读的过程中是否有写入,这是一种乐观的读锁,O(∩_∩)O哈哈~。 显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。
  5. 锁降级设计思想:
    1. 简单讲:希望写后可以立刻读,不希望被其他写线程抢占并修改。那么释放写锁之前先加读锁
    2. 锁降级 下面的示例代码摘自ReentrantWriteReadLock源码中:ReentrantWriteReadLock支持锁降级,遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁,不支持锁升级。解读在最下面:
      请添加图片描述
    3. 解读:
      1. 代码中声明了一个volatile类型的cacheValid变量,保证其可见性。
      2. 首先获取读锁,如果cache不可用,则释放读锁,获取写锁,在更改数据之前,再检查一次cacheValid的值,然后修改数据,将cacheValid置为true,然后在释放写锁前获取读锁;此时,cache中数据可用,处理cache中数据,最后释放读锁。这个过程就是一个完整的锁降级的过程,目的是保证数据可见性。
      3. 如果违背锁降级的步骤 如果当前的线程C在修改完cache中的数据后,没有获取读锁而是直接释放了写锁,那么假设此时另一个线程D获取了写锁并修改了数据,那么C线程无法感知到数据已被修改,则数据出现错误。
      4. 如果遵循锁降级的步骤 线程C在释放写锁之前获取读锁,那么线程D在获取写锁时将被阻塞,直到线程C完成数据处理过程,释放读锁。这样可以保证返回的数据是这次更新的数据,该机制是专门为了缓存设计的。

14.2 邮戳锁(版本锁)

  1. 比读写锁更快的锁与锁饥饿问题相关优化
  2. 是什么?
    1. StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化。
    2. 邮戳锁,也叫票据锁
    3. stamp (戳记, Iong类型):代表了锁的状态。当stamp返回零时,表示线程获取锁失败。并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值。
    4. 解决问题:写锁饥饿问题;公平策略可以解决,但是牺牲了吞吐量
  3. StampedLock类的乐观读锁闪亮登场
    1. ReentrantReadWriteLock的读锁被占用的时候,其他线程尝试获取写锁的时候会被阻塞。但是,StampedLock采取乐观获取锁后,其他线程尝试获取写锁时不会被阻塞,这其实是对读锁的优化,所以,在获取乐观读锁后,还需要对结果进行校验。
    2. 简单说:读锁默认不会有写锁对数据进行修改,但是读完会校验版本,如果被改了则此次读作
  4. StampedLock特点:
    1. 所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功:
    2. 所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致:
    3. StampedLock是不可重入的,危险(如果一个线程己经持有了写锁,再去获取写锁的话就会造成死锁)
    4. StampedLock有三种访问模式
      1. Reading(读模式悲观):功能和ReentrantReadWriteLock的读锁类似
      2. Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
      3. Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式
  5. API:基本和读写锁一致,多了一个validate校验方法
  6. 代码演示:
    1. stampedLock完全可以作为读写锁用:

      //悲观读
      public void read() {
              long stamp = stampedLock.readLock();
              System.out.println(Thread.currentThread().getName()+"\t come in readlock block,4 seconds continue...");
              //暂停4秒钟线程
              for (int i = 0; i <4 ; i++) {
                  try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                  System.out.println(Thread.currentThread().getName()+"\t 正在读取中......");
              }
              try {
                  int result = number;
                  System.out.println(Thread.currentThread().getName()+"\t"+" 获得成员变量值result:" + result);
                  System.out.println("写线程没有修改值,因为 stampedLock.readLock()读的时候,不可以写,读写互斥");
              }catch (Exception e){
                  e.printStackTrace();
              }finally {
                  stampedLock.unlockRead(stamp);
              }
          }
      
      public void write() {
              long stamp = stampedLock.writeLock();
              System.out.println(Thread.currentThread().getName()+"\t"+"=====写线程准备修改");
              try {
                  number = number + 13;
              }catch (Exception e){
                  e.printStackTrace();
              }finally {
                  stampedLock.unlockWrite(stamp);
              }
              System.out.println(Thread.currentThread().getName()+"\t"+"=====写线程结束修改");
      }
      
      public static void main(String[] args) {
              StampedLockDemo resource = new StampedLockDemo();
      
              //1 悲观读,和ReentrantReadWriteLock一样
              new Thread(() -> {
                  //悲观读
                  resource.read();
              },"readThread").start();
      
              new Thread(() -> {
                  resource.write();
              },"writeThread").start();
      }
      
      修改是失败的,读取成功
      
    2. 乐观读:

      // 乐观读
      public void tryOptimisticRead() {
          long stamp = stampedLock.tryOptimisticRead();
          // 先把数据取得一次
          int result = number;
          // 间隔4秒钟,我们很乐观的认为没有其他线程修改过number值,愿望美好,实际情况靠判断。
          System.out.println("4秒前stampedLock.validate值(true无修改,false有修改)" + "\t" + stampedLock.validate(stamp));
          for (int i = 1; i <= 4; i++) {
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println(Thread.currentThread().getName() + "\t 正在读取中......" + i +
                      "秒后stampedLock.validate值(true无修改,false有修改)" + "\t"
                      + stampedLock.validate(stamp));
          }
          if (!stampedLock.validate(stamp)) {
              System.out.println("有人动过--------存在写操作!");
              // 有人动过了,需要从乐观读切换到普通读的模式。
              stamp = stampedLock.readLock();
              try {
                  System.out.println("从乐观读 升级为 悲观读并重新获取数据");
                  // 重新获取数据
                  result = number;
                  System.out.println("重新悲观读锁通过获取到的成员变量值result:" + result);
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  stampedLock.unlockRead(stamp);
              }
          }
          System.out.println(Thread.currentThread().getName() + "\t finally value: " + result);
      }
      
  7. stampedLock缺点:
    1. StampedLock 不支持重入,没有Re开头
    2. StampedLock 的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意。
    3. 使用 StampedLock一定不要调用中断操作,即不要调用interrupt( )方法

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

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

相关文章

8通道250MSPS采样率16位AD采集FMC子卡-高速数据采集专家

FMC128是一款8通道250MHz采样率16位分辨率AD采集FMC子卡&#xff0c;符合VITA57.1规范&#xff0c;可以作为一个理想的IO模块耦合至FPGA前端&#xff0c;8通道AD将模拟信号数字化后通过高带宽的FMC连接器&#xff08;HPC&#xff09;连接至FPGA&#xff0c;从而大大降低了系统信…

电力智能运维是什么?有哪些优势?

设备检修维护是指对设备和系统进行必要的监视、维修和养护&#xff0c;通过日常的维护使设备保持良好的状态&#xff0c;确保设备安全、稳定、经济运行。由于时代的变迁&#xff0c;电力设备的检测维修变得也越来越智能化。采用的智能运维系统&#xff0c;可以更好的监控电力设…

BCSP-玄子Java开发之Java Web编程CH01_初识动态网页

BCSP-玄子Java开发之Java Web编程CH01_初识动态网页 1.1 B/S架构 B/S架构&#xff1a;浏览器/服务器 程序完全部署在服务器上使用浏览器访问服务器无需单独安装客户端软件 为什么要使用B/S架构 B/S与C/S比较B/S架构C/S架构软件安装浏览器需要专门的客户端应用升级维护客户…

NGINX PHP Cookie 会话中 PHPSESSID 缺少 HTTPOnly、Secure 属性解决方案

NGINX & PHP Cookie 会话中 PHPSESSID 缺少 HTTPOnly、Secure 属性解决方案 1 / 说明 基于安全的考虑&#xff0c;需要给cookie加上Secure和HttpOnly属性&#xff0c;HttpOnly比较好理解&#xff0c;设置HttpOnlytrue的cookie不能被js获取到&#xff0c;无法用document.coo…

linux系统的文件等相关操作命令

文章目录 1 查找文件&#xff08;find、grep&#xff09;2 查看文件内容&#xff08;cat、more、less、head、tail&#xff09;3 文件比较&#xff08;diff&#xff09;4 文本编辑器&#xff08;vi、vim&#xff09;5 文件压缩与解压&#xff08;tar、tar.tgz、zip、rar、rar.g…

yum安装 lnmp

目录 一.nginx 的yum安装 1.关闭防火墙 2. 安装 nginx 3.安装依赖包 4.启动服务 二. 安装 mysql 5.7 (mariadb) 1.nysal的yum安装 2. 启动服务 3.在日志文件中找出root用户的初始密码 4.登录 mysql 5. 停止版本更新&#xff0c;稳定数据库的运行 三.php 的yum安装 1.…

rust abc(4): 定义变量并使用 mut 关键字

文章目录 1. 目的2. 不可变变量 (immutable variable)2.1 含义2.2 代码实例 3. 可变变量 (mutable variable)3.1 含义3.2 代码实例 4. 总结 1. 目的 学习 rust 语言中变量的定义方式&#xff0c; 包括普通变量&#xff08;immutable&#xff09;、可变变量&#xff08;mutable…

轻量云服务器(香港)ping不通怎么解决?

​  在使用轻量云服务器(香港)时&#xff0c;有时候会出现ping不通的情况&#xff0c;这时候我们该怎么办呢? 首先&#xff0c;我们需要知道 ping 不通的原因。 ping 是一种基于 ICMP 协议的网络测试工具&#xff0c;它可以用来测试网络连接的质量和速度。如果 ping 不通&am…

英特尔进军晶圆代工,台积电“危”?

近来&#xff0c;半导体市场再次变得繁荣&#xff0c;尤其随着AI大型机模型的出现&#xff0c;半导体巨头们纷纷加大投资力度&#xff0c;以期在AI时代中积蓄新的增长。 作为AI大模型时代中最受益的厂商之一&#xff0c;英伟达稳居市场前沿&#xff0c;而AMD也加入了竞争&…

阿里发布2023年Java社招岗(正式版)面试题

每年的金三银四、金九银十都是各大公司招聘程序员的最佳时期&#xff0c;在这段时间内有好多程序员为面试而发愁&#xff0c;不知道如何才能收到好的 offer&#xff0c;拿到理想的薪资&#xff0c;实现自我的人生价值&#xff01; 我想告诉大家的是&#xff0c;其实都不用愁的…

Web Worker是什么?怎么用?

71. Web Worker是什么&#xff1f;怎么用&#xff1f; Web Worker 是一种浏览器提供的 JavaScript 特性&#xff0c;它允许在后台线程中运行脚本&#xff0c;从而避免阻塞主线程并提高页面性能和响应速度。 1. Web Worker 的使用方法如下&#xff1a; 创建 Worker 对象&…

chatgpt赋能python:Python编译成库的利与弊

Python编译成库的利与弊 Python作为一种高级编程语言&#xff0c;具有简洁易读的语法和强大的生态系统&#xff0c;在数据科学、Web开发、游戏开发等领域得到广泛应用。然而&#xff0c;Python解释器的执行效率较低&#xff0c;因此为了提高Python程序的性能&#xff0c;常使用…

【嵌入式环境下linux内核及驱动学习笔记-(18)内核驱动模块的启动机制】

目录 1、module_init宏1.1 展开1.2 解释以下几个标识1.2.1 fn1.2.2 id1.2.3 类型 initcall_t &#xff1a;1.2.4 __used1.2.5 __init1.2.6 __attribute__ 1.3 实例说明 2、 驱动启动机制2.1 initcall_t 类型的数组2.2.1 __initcallx_start数组2.2.2 initcall_levels[]数组 2.3 …

每日一练 | 华为认证真题练习Day64

1、如下图所示的网络&#xff0c;所有路由器运行0SPF协议&#xff0c;链路上方为Cost值的大小&#xff0c;则RA路由表中到达网络10.0.0.0/8的Cost值是多少&#xff1f; A. 70 B. 20 C. 60 D. 100 2、如下图所示的网络&#xff0c;主机A没有配置网关&#xff0c;主机B存在网关…

基于GEC6818 Qt智能病房监控系统

文章目录 一、项目设备及平台二、项目功能说明1. 整体功能2. GEC6818开发板功能介绍3. GY39模块功能介绍4. MQ-2型烟雾传感器功能介绍5. RFID模块 三、硬件系统设计实现与图表四、软件系统设计实现与流程图1. 软件系统设计总体描述2. 软件实现流程图3. 操作过程 五、调试过程中…

基于电容电流前馈与电网电压全前馈的单相LCL并网逆变器谐波抑制MATLAB仿真(电压比例反馈及一二次微分反馈)

基于电容电流前馈与电网电压全前馈的单相LCL并网逆变器谐波抑制MATLAB仿真&#xff08;电压比例反馈及一二次微分反馈&#xff09;资源-CSDN文库https://download.csdn.net/download/weixin_56691527/87941037模型简介&#xff1a; 参考阮新波教授【LCL型并网逆变器的控制技术…

scrapy的数据保存到数据库

将数据保存到数据库 mysql数据库 下载链接数据库的依赖 Conda/pip install pymysql在piplines.py 文件中 重写open_spider方法 ​ 连接到mysql数据库 def open_spider(self, spider):self.conn pymysql.Connect(hostlocalhost,port3306,userroot,password20020115,dbscrap…

go系列-读取文件

1 概述 2 整个文件读入内存 直接将数据直接读取入内存&#xff0c;是效率最高的一种方式&#xff0c;但此种方式&#xff0c;仅适用于小文件&#xff0c;对于大文件&#xff0c;则不适合&#xff0c;因为比较浪费内存。 2.1 直接指定文化名读取 在 Go 1.16 开始&#xff0c;i…

chatgpt赋能python:Python编程语言制作的著名游戏

Python编程语言制作的著名游戏 Python是一种广泛使用的编程语言&#xff0c;其简单易读的语法让其成为许多游戏开发者的首选。本文将介绍利用Python编程语言制作的几个著名游戏&#xff0c;并将着重标记加粗它们的标题&#xff0c;以便于读者更容易了解。 1. 游戏&#xff1a…

【Python】python入门篇

概述 官网 https://www.python.org/ Python 是一种脚本语言&#xff08;scripting language&#xff09;。 与编译型语言&#xff08;如 C 和 C&#xff09;不同&#xff0c;Python 的程序代码不需要进行显式的编译&#xff0c;在执行时会动态地解释执行代码。 Python 的脚本执…