【JUC2022】第七章 AQS、ReentrantReadWriteLock 和 StampedLock

news2024/11/25 15:42:38

【JUC2022】第七章 AQS

文章目录

  • 【JUC2022】第七章 AQS
  • 一、AQS
    • 1.概述
    • 2.同步器
    • 3.抽象的
    • 4.队列式
  • 二、ReentrantReadWriteLock
    • 1.概述
    • 2.案例
    • 3.存在的问题
  • 三、StampedLock
    • 1.概述
    • 2.案例
    • 3.存在的问题

一、AQS

1.概述

在这里插入图片描述
AQS(AbstractQueueSynchronizer,抽象的队列式同步器),它提供了一个基于 FIFO 队列,可以用于构建锁或者其他相关同步器的基础框架,许多同步类的实现都依赖于它,比如 ReentrantLock、Semaphore、CountDownLatch

AQS 实现了 3 点基本功能:

  • 同步器基本范式、结构
  • 线程的阻塞、唤醒机制
  • 线程阻塞队列的维护

我们来看一下 java.util.concurrent.locks 的类关系图
请添加图片描述
上图中,Lock 的实现类其实都是构建在 AQS 上的,但为何没用 UML 线表示它们之间的关系呢?这是因为每个 Lock 实现类都持有自己内部类 Sync 的实例,而这个 Sync 才是继承自 AQS 的。那为何要实现不同的 Sync 呢?这是因为不同的 Lock 的用途不同

2.同步器

多线程环境下,线程之间可以通过某种状态来同步,比如只有当状态满足某种条件,才能触发线程执行某种操作,能实现这个特性的可以称之为同步器

AQS 里有一个最关键的属性 private volatile int state,可以理解将它为资源数量的抽象。AQS 提供了 getState 和 setState 方法,还有一个线程安全的 compareAndSetState 方法,它利用了 Unsafe 的 CAS 操作,可以做到在并发场景下对 state 进行原子性的修改,并且可以获得修改结果。正式因为这个特性,可以使用 AQS 构建同步器

3.抽象的

AQS 是一个抽象类,需要被子类继承并且重写其中的一些方法,官方对此做了一些说明:AQS 的子类必须重写那些会更改 state 的 protected 方法,以及定义 state 的何种状态意味着需要阻塞。比如,如果要实现一个锁,则 state 可以定义为:0 表示未锁定,1 表示锁定。如果要实现信号量,state 可以表示资源甚于

为此,AQS 提供了以下方法:

  • tryAcquire(int): 试图在独占模式下获取对象的状态
  • tryRelaease(int): 试图设置状态来反映独占模式下的释放
  • tryAcquireShared(int): 试图在共享模式下获取对象的状态
  • tryRelaease(int): 试图设置状态来反映共享模式下的释放
  • isHeldExclusively(): 如果对于当前线程,同步是以独占方式进行的,则返回 true

4.队列式

AQS 将阻塞队列线程封装到了一个内部类 Node 里,并维护了一个 CLH Node FIFO 队列。CLH 队列是一个非阻塞的 FIFO 队列,也就是说,往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证原子性,实现无锁且快速的修改操作
在这里插入图片描述

以 ReentrantLock 的加锁过程为例:

  1. 尝试加锁
  2. 加锁失败,线程加入队列
  3. 线程入队后进入阻塞状态

二、ReentrantReadWriteLock

1.概述

读写锁定义为:一个资源能够被多个读线程访问,或者被一个写线程访问,并且读写线程不能同时访问

在读多写少的场景下,读写锁具有较高的性能体现

2.案例

package com.sisyphus.ReentrantReadWriteLock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyResource{
    Map<String, String> map = new HashMap<>();

    Lock lock = new ReentrantLock();

    ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void write(String key, String value){
        rwLock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t" + "正在写入");
            map.put(key, value);
            try{
                TimeUnit.MILLISECONDS.sleep(500);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "完成写入" + value);
        }finally {
            rwLock.writeLock().unlock();
        }
    }

    public void read(String key){
        rwLock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t" + "正在读取");
            String result = map.get(key);
            try{
                TimeUnit.MILLISECONDS.sleep(200);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "完成读取" + result);
        }finally {
            rwLock.readLock().unlock();
        }
    }
}

public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        MyResource myResource = new MyResource();

        for(int i = 0; i < 10; i++){
            int finalI = i;
            new Thread(()->{
                myResource.write(finalI + "", finalI + "");
            },String.valueOf(i)).start();
        }

        for(int i = 0; i < 10; i++){
            int finalI = i;
            new Thread(()->{
                myResource.read(finalI + "");
            },String.valueOf(i)).start();
        }

        for(int i = 0; i < 3; i++){
            int finalI = i;
            new Thread(()->{
                myResource.write(finalI + "", finalI + "");
            },"新写线程" + String.valueOf(i)).start();
        }
    }
}

3.存在的问题

写线程饥饿

当读锁被获取时,写锁无法获取,必须等待读锁释放。如果读线程太多,那么写线程将一直被阻塞,使用“公平锁”可以一定程度上缓解这个问题,但是“公平锁”是以牺牲系统吞吐量为代价的

锁降级

锁的颗粒度减小叫锁升级,锁的颗粒度增大叫锁降级。遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级为读锁。但是无法从读锁升级为写锁,这也是会造成写线程饥饿的原因

三、StampedLock

1.概述

StampedLock 是 JDK1.8 中新增的一个读写锁,是对 JDK1.5 中的 ReentrantReadWriteLock 的优化

StampedLock 有一个重要属性 long stamp,代表了锁的状态。所有获取锁的方法,都会返回一个 stamp,stamp 为 0表示失败;所有释放锁的方法,都需要传入一个 stamp,并且这个 stamp 必须是和成功获取锁得到的 stamp 一致

StampLock 是不可重入的,如果一个线程已经持有了写锁,再去获取写锁将会导致死锁

StampLock 有三种访问模式:

  • Reading(悲观读模式):功能和 ReentrantReadWriteLock 的读锁类似
  • Writeing(写模式):功能和 ReentrantReadWriteLock 的写锁类似
  • Optimistic reading(乐观读模式):无锁机制,支持读写并发,先乐观地认为读取时没人修改,判断被修改再升级为悲观读模式

2.案例

package com.sisyphus.StampedLockDemo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;

public class StampedLockDemo {
    static int number = 1;
    static StampedLock stampedLock = new StampedLock();

    public void write(){
        long stamp = stampedLock.writeLock();
        System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改");
        try{
            number = number + 1;
        }finally {
            stampedLock.unlock(stamp);
        }
        System.out.println(Thread.currentThread().getName() + "\t" + "写线程结束修改");
    }

    //悲观读
    public void read(){
        long stamp = stampedLock.readLock();
        System.out.println(Thread.currentThread().getName() + "\t" + "come in readLock codeBlock lock, 4 seconds continue ...");
        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("写线程没有修改成功,读锁未释放,写锁无法接入,传统的读写互斥");
        }finally {
            stampedLock.unlockRead(stamp);
        }
    }

    //乐观读
    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 = 0; 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有修改)" + stampedLock.validate(stamp));
        }
        if(!stampedLock.validate(stamp)){
            System.out.println("有人修改过-----有写操作");
            stamp = stampedLock.readLock();
            try{
                System.out.println("从乐观读升级为悲观读");
                result = number;
                System.out.println("重新悲观读后 result:" + result);
            }finally {
                stampedLock.unlockRead(stamp);
            }
        }
        System.out.println(Thread.currentThread().getName() + "\t" + " finally value:" + result);
    }

    public static void main(String[] args) {
        StampedLockDemo resource = new StampedLockDemo();

        /*传统版
        new Thread(()->{
            resource.read();
        },"readThread").start();

        try{
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");
            resource.write();
        },"writeThread").start();

        try{
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "\t" + "number:" + number);*/

        new Thread(()->{
            resource.tryOptimisticRead();
        },"readThread").start();

        //暂停 2 秒钟,演示读过程可以介入写操作
        try{
            TimeUnit.SECONDS.sleep(2);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");
            resource.write();
        },"writeThread").start();
    }
}

3.存在的问题

  • StampedLock 不支持重入
  • StampedLock 的悲观读锁和写锁都不支持条件变量 Condition

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

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

相关文章

tesseract -图像识别

下载链接&#xff1a;https://digi.bib.uni-mannheim.de/tesseract/如下选择最新的版本&#xff0c;这里我选择tesseract-ocr-w64-setup-5.3.0.20221222.exe有如下python模块操作tesseractpyocr 国内源&#xff1a;pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ py…

ThreadLocal 学习常见问题

ThreadLocal 这个此类提供线程局部变量。这些变量不同于通常的对应变量&#xff0c;因为每个访问一个变量的线程(通过 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是希望将状态与线程(例如&#xff0c;用户 ID 或事务 ID)关联的类中的私有静态字段。使…

vue router elementui template CDN模式实现多个页面跳转

文章目录前言一、elementui Tabs标签页和NavMenu 导航菜单是什么&#xff1f;二、使用方式1.代码如下2.页面效果总结前言 写上一篇bloghttps://blog.csdn.net/jianyuwuyi/article/details/128959803的时候因为整个前端都写在一个index.html页面里&#xff0c;为了写更少的代码…

CENTO OS上的网络安全工具(十九)ClickHouse集群部署

一、VMware上集群部署ClickHouse &#xff08;一&#xff09;网络设置 1. 通过修改文件设置网络参数 &#xff08;1&#xff09;CentOS 在CENTOS上的网络安全工具&#xff08;十六&#xff09;容器特色的Linux操作_lhyzws的博客-CSDN博客中我们提到过可以使用更改配置文件的方式…

推荐 7 个 Vue.js 插件,也许你的项目用的上(五)

当我们可以通过使用库轻松实现相同的结果时&#xff0c;为什么还要编写自定义功能&#xff1f;开发人员最好的朋友和救星就是这些第三方库。我相信一个好的项目会利用一些可用的最佳库。Vue.js 是创建用户界面的最佳 JavaScript 框架之一。这篇文章是关于 Vue.js 的优秀库系列的…

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 难度&#xff1a;easy\color{Green}{easy}easy 题目描述 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表…

4-3 Linux启动流程

文章目录前言经典启动流程1 按下电源2 开机自检(BIOS)3 MBR引导4 GRUB菜单5 加载内核6 运行init进程7 读取/etc/inittab8 读取/etc/rc.sysinit初始化系统9 运行/etc/rc.d/rcN.d/脚本10 /etc/rc.local11 登录页面logincentos7与centos6前言 Linux系统的启动过程并不是大家想象中…

防静电监控仪可以检测现场设备是否和实际大地接触

随着电子产品集成化度越来越高&#xff0c;对于电子产品装配来说&#xff0c;静电的危害严重影响到产品的质量、成品率和可靠性, 必须对用于电子产品装配的净化间进行系统防静电措施&#xff0c;将生产过程中的静电危害程度降至最低。近年来电子企业对ESD的危害的深入认识&…

代码随想录刷题-数组-二分查找

文章目录写在前面原理习题题目1思路和代码题目-2写在前面 这个专栏是记录我刷代码随想录过程中的随想和总结。每一小节都是根据自己的理解撰写的&#xff0c;文章比较短&#xff0c;主要是为了记录和督促自己。刷完一章后&#xff0c;我会再单独整理一篇文章来总结和分享。 本…

【JVM 由浅入深】JVM入门

JVM入门1. 概述 今天我们对JVM 进行入门讲解&#xff0c;让我们了解下什么是JVM&#xff0c;是专门为Java服务的一款产品吗&#xff1f;&#xff1f;&#xff1f; 好了废话不多说了&#xff0c;让我们开始吧 2. 详解 2.1 Java 是跨平台的 为什么是Java是跨平台的呢&#xff0c…

LeetCode 路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 叶子节点 是指没有子节点…

oneblog_justauth_三方登录配置【Github】

文章目录oneblog添加第三方平台github中创建三方应用完善信息登录oneblog添加第三方平台 1.oneblog管理端&#xff0c;点击左侧菜单 网站管理——>社会化登录配置管理 ,添加一个社会化登录 2.编辑信息如下&#xff0c;选择github平台后复制redirectUri,然后去github获取cl…

Arduino添加ESP32开发板

【2023年3月4日】 最近要在新电脑上安装Arduino&#xff0c;需要进行一些配置&#xff0c;正好记录一下&#xff01; Arduino2.0.1 下的开发板添加操作。 ESP32开发板GitHub链接&#xff1a; GitHub - espressif/arduino-esp32: Arduino core for the ESP32Arduino core for…

ICASSP2023|达摩院语音实验室入选论文全况速览

近日&#xff0c;语音技术领域国际会议ICASSP公布了本届论文审稿结果&#xff0c;阿里巴巴达摩院语音实验室有14篇论文被大会收录。本次被接收的论文研究方向涵盖语音识别、语音唤醒、语音增强、说话人日志、语义理解、多模态预训练等。 01 TOLD: A Novel Two-Stage Overlap-…

实验楼项目

创建虚拟环境命令&#xff1a;python -m venv venv 项目整体分为六个个模块&#xff1a;用户信息相关模块、机构相关模块、课程相关模块、用户操作相关模块&#xff0c;评论区模块 搜索模块。 # 项目功能概括&#xff1a; 1、首先具有完整的用户登录&#xff0c;邮箱注册以及…

JDBC的实现(IDEA版)

前期准备 开发环境&#xff1a; IDEA 2021.1.3 JAVA 1.8 MYSQL 8.0.32 msql用户名:root 密码&#xff1a;123 下载MySQL JDBC 驱动 前往MySQL官网下载对应版本的MySQL Connector/J驱动 &#xff08;下载地址&#xff1a;https://dev.mysql.com/downloads/connector/j/&#xff…

基于rootfs构建Docker镜像

1. 背景 在实际工作中&#xff0c;由于系统本身版本过低&#xff0c;在接受新项目时出现系统版本过低而无法开始工作的问题。 为了解决该问题&#xff0c;使用Docker构建基于ubuntu-18.04的Docker镜像&#xff0c;以解决版本兼容问题。 2. 构建rootfs 2.1. 下载ubuntu-18.0…

HCIA复习1

HCIA复习 抽象语言---->编码 编码---->二进制 二进制--->电信号 处理电信号 OSI参考模型----OSI/RM 应用层 表示层 会话层 传输层 端口号&#xff1a;0-65535&#xff1b;1-1023是注明端口 网络层 IP地址 数据链路层 物理层 ARP协议 正向ARP---通过IP地址获取目的MAC地…

云服务器Ubuntu(无桌面)安装远程桌面

如果主机上安装的ubuntu是桌面版&#xff0c;打开桌面共享即可使用Windows的远程桌面进行链接。详细参考ubuntu20.10中设置桌面共享的三种方式(任选其一) 的第一部分。 本文主要说明如何在Ubuntu云服务器上安装远程桌面。 一、安装桌面环境 在 Ubuntu 源仓库有很多桌面环境供…

路由协议(OSPF、ISIS、BGP)实验配置

目录 OSPF基础实验 建立OSPF邻居 配置虚连接 配置接口的网络类型 配置特殊区域 配置路由选路 配置路由过滤 ISIS基础实验配置 配置ISIS邻居建立 配置认证 配置路由扩散 配置路由过滤 配置定时器 BGP基础实验配置 建立BGP对等体 建立IBGP对等体 建立EBGP对等体…