(十七) 共享模型之工具【JUC】【读写锁】

news2024/12/23 22:40:39

一、ReentrantReadWriteLock(P247)

当读操作远远高于写操作时,这时候使用 读写锁 - 可以并发,提高性能。 类似于数据库中的 select ... from ... lock in share mode
提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法
@Slf4j(topic = "c.TestReadWriteLock")
public class TestReadWriteLock {
    public static void main(String[] args) throws InterruptedException {
        DataContainer dataContainer = new DataContainer();
        new Thread(() -> {
            dataContainer.read();
        }, "t1").start();

        new Thread(() -> {
            dataContainer.read();
        }, "t2").start();
    }
}

@Slf4j(topic = "c.DataContainer")
class DataContainer {
    private Object data;
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock r = rw.readLock();
    private ReentrantReadWriteLock.WriteLock w = rw.writeLock();

    public Object read() {
        log.debug("获取读锁...");
        r.lock();
        try {
            log.debug("读取");
            sleep(1);
            return data;
        } finally {
            log.debug("释放读锁...");
            r.unlock();
        }
    }

    public void write() {
        log.debug("获取写锁...");
        w.lock();
        try {
            log.debug("写入");
            sleep(1);
        } finally {
            log.debug("释放写锁...");
            w.unlock();
        }
    }
}

注意事项
(1)读锁不支持条件变量。
(2)重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待

(3) 重入时降级支持:即持有写锁的情况下去获取读锁。

二、 * 应用之缓存

1. 缓存更新策略

更新时,是先清缓存还是先更新数据库

2. 读写锁实现一致性缓存

public class TestGenericDao {
    public static void main(String[] args) {
        GenericDao dao = new GenericDaoCached();
        System.out.println("============> 查询");
        String sql = "select * from emp where empno = ?";
        int empno = 7369;
        Emp emp = dao.queryOne(Emp.class, sql, empno);
        System.out.println(emp);
        emp = dao.queryOne(Emp.class, sql, empno);
        System.out.println(emp);
        emp = dao.queryOne(Emp.class, sql, empno);
        System.out.println(emp);

        System.out.println("============> 更新");
        dao.update("update emp set sal = ? where empno = ?", 800, empno);
        emp = dao.queryOne(Emp.class, sql, empno);
        System.out.println(emp);
    }
}

class GenericDaoCached extends GenericDao {
    private GenericDao dao = new GenericDao();
    private Map<SqlPair, Object> map = new HashMap<>();
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();

    @Override
    public <T> List<T> queryList(Class<T> beanClass, String sql, Object... args) {
        return dao.queryList(beanClass, sql, args);
    }

    @Override
    public <T> T queryOne(Class<T> beanClass, String sql, Object... args) {
        // 先从缓存中找,找到直接返回
        SqlPair key = new SqlPair(sql, args);;
        rw.readLock().lock();
        try {
            T value = (T) map.get(key);
            if(value != null) {
                return value;
            }
        } finally {
            rw.readLock().unlock();
        }
        rw.writeLock().lock();
        try {
            // 多个线程
            T value = (T) map.get(key);
            if(value == null) {
                // 缓存中没有,查询数据库
                value = dao.queryOne(beanClass, sql, args);
                map.put(key, value);
            }
            return value;
        } finally {
            rw.writeLock().unlock();
        }
    }

    @Override
    public int update(String sql, Object... args) {
        rw.writeLock().lock();
        try {
            // 先更新库
            int update = dao.update(sql, args);
            // 清空缓存
            map.clear();
            return update;
        } finally {
            rw.writeLock().unlock();
        }
    }

    class SqlPair {
        private String sql;
        private Object[] args;

        public SqlPair(String sql, Object[] args) {
            this.sql = sql;
            this.args = args;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            SqlPair sqlPair = (SqlPair) o;
            return Objects.equals(sql, sqlPair.sql) &&
                    Arrays.equals(args, sqlPair.args);
        }

        @Override
        public int hashCode() {
            int result = Objects.hash(sql);
            result = 31 * result + Arrays.hashCode(args);
            return result;
        }
    }

}

注意
以上实现体现的是读写锁的应用,保证缓存和数据库的一致性,但有下面的问题没有考虑
1️⃣适合读多写少,如果写操作比较频繁,以上实现性能低
2️⃣没有考虑缓存容量
3️⃣没有考虑缓存过期
4️⃣只适合单机
5️⃣并发性还是低,目前只会用一把锁
6️⃣更新方法太过简单粗暴,清空了所有 key (考虑按类型分区或重新设计 key

三、* 读写锁原理

1. 图解流程

读写锁用的是同一个 Sycn 同步器,因此等待队列、 state 等也是同一个

1.1 t1 w.lockt2 r.lock

(1)t1 成功上锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,而读锁使用的是 state 的高 16 位

(2)t2 执行 r.lock,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared 返回 -1 表示失败

tryAcquireShared 返回值表示

1️⃣-1 表示失败

2️⃣0 表示成功,但后继节点不会继续唤醒

3️⃣正数表示成功,而且数值是还有几个后继节点需要唤醒,读写锁返回 1

(3)这时会进入 sync.doAcquireShared(1) 流程,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时 t2 仍处于活跃状态

(4)t2 会看看自己的节点是不是老二,如果是,还会再次调用 tryAcquireShared(1) 来尝试获取锁

(5)如果没有成功,在 doAcquireShared for (;;) 循环一次,把前驱节点的 waitStatus 改为 -1,再 for (;;) 循环一次尝试 tryAcquireShared(1) 如果还不成功,那么在 parkAndCheckInterrupt() park

1.2 t3 r.lockt4 w.lock

这种状态下,假设又有 t3 加读锁和 t4 加写锁,这期间 t1 仍然持有锁,就变成了下面的样子

1.3 t1 w.unlock

2. 源码分析

四、StampedLock

该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用

加解读锁

long stamp = lock.readLock();
lock.unlockRead(stamp);

加解写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
乐观读, StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次 戳校验 如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。
long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
   // 锁升级
}
提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法
@Slf4j(topic = "c.TestStampedLock")
public class TestStampedLock {
    public static void main(String[] args) {
        DataContainerStamped dataContainer = new DataContainerStamped(1);
        new Thread(() -> {
            dataContainer.read(1);
        }, "t1").start();
        sleep(0.5);
        new Thread(() -> {
            dataContainer.read(0);
        }, "t2").start();
    }
}

@Slf4j(topic = "c.DataContainerStamped")
class DataContainerStamped {
    private int data;
    private final StampedLock lock = new StampedLock();

    public DataContainerStamped(int data) {
        this.data = data;
    }

    public int read(int readTime) {
        long stamp = lock.tryOptimisticRead();
        log.debug("optimistic read locking...{}", stamp);
        sleep(readTime);
        if (lock.validate(stamp)) {
            log.debug("read finish...{}, data:{}", stamp, data);
            return data;
        }
        // 锁升级 - 读锁
        log.debug("updating to read lock... {}", stamp);
        try {
            stamp = lock.readLock();
            log.debug("read lock {}", stamp);
            sleep(readTime);
            log.debug("read finish...{}, data:{}", stamp, data);
            return data;
        } finally {
            log.debug("read unlock {}", stamp);
            lock.unlockRead(stamp);
        }
    }

    public void write(int newData) {
        long stamp = lock.writeLock();
        log.debug("write lock {}", stamp);
        try {
            sleep(2);
            this.data = newData;
        } finally {
            log.debug("write unlock {}", stamp);
            lock.unlockWrite(stamp);
        }
    }
}
注意
StampedLock 不支持条件变量
StampedLock 不支持可重入

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

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

相关文章

【论文阅读 CIKM‘2021】Learning Multiple Intent Representations for Search Queries

文章目录Original PaperMotivationMethodTask Description and Problem FormulationNMIR Framework: A High-Level OverviewModel Implementation and TrainingDataOriginal Paper Learning Multiple Intent Representations for Search Queries More related papers can be …

基于Electron的桌面端应用开发和实践

引言 如果开发跨桌面端的应用开发的话&#xff0c;我相信&#xff0c;electron目前绝对是不可避免的技术方案。web应用大家都知道&#xff0c;通过浏览器访问的应用就是web应用&#xff0c;那什么是桌面端&#xff1f;桌面端有两个重要特点&#xff1a; 具备独立运行于操作系…

学习压力容器中卡箍快开结构的强度计算

导读:压力容器的设计一定要考虑安全性、经济性、环保及健康问题。首先安全是核心问题&#xff0c;在保证安全的前提下尽可能的再做到经济合理。 本文从强度计算软件SW6-2011 V3.1补丁二&#xff08;单机版&#xff09;和&#xff08;网络版&#xff09;所解决的问题&#xff0…

Redis 性能问题优化方案

Redis性能问题&优化方案前言Redis真的变慢了吗&#xff1f;使用复杂度过高的命令操作bigkey集中过期实例内存达到上限fork耗时严重开启内存大页开启AOF绑定CPU使用Swap碎片整理网络带宽过载其他原因频繁短连接运维监控其它程序争抢资源总结前言 Redis 作为优秀的内存数据库…

Java高效率复习-MySQL上篇[MySQL]

前言 本文章是用于总结尚硅谷MySQL教学视频的记录文章&#xff0c;主要用于复习&#xff0c;非商用 原视频连接&#xff1a;https://www.bilibili.com/video/BV1iq4y1u7vj/?p21&spm_id_frompageDriver&vd_sourcec4ecde834521bad789baa9ee29af1f6c https://www.bilib…

Spring Boot 项目优化和 JVM 调优,亲测!真实有效。。

三、Jvm调优实战 1、未设置JVM参数的情况 我现在有一个项目&#xff0c;默认情况下&#xff0c;没有设置任何Jvm参数。 下面我来启动看一下。 看一下堆栈分配&#xff1a; 很明显默认的最大堆内存分配了8个G。很明显的不合理嘛。 2、下面我们来设置下Jvm参数 例如要配置JVM…

vue2 ElementUI 表单标签、表格表头添加问号图标提示

文章目录1. 问题背景2. element-ui悬浮提示定义3. 基础4. 延申5. 参考1. 问题背景 使用element-ui有时候需要对表格的表头、表单的标签进行自定义&#xff0c;添加问号的悬浮提示。 要达到的效果&#xff0c;如图所示&#xff1a; 2. element-ui悬浮提示定义 https://elemen…

【菜菜的sklearn课堂笔记】聚类算法Kmeans-基于轮廓系数来选择n_clusters

视频作者&#xff1a;菜菜TsaiTsai 链接&#xff1a;【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili 我们通常会绘制轮廓系数分布图和聚类后的数据分布图来选择我们的最佳n_clusters from sklearn.metrics import silhouette_samples,silhouette_s…

c++还原简单的vector

文章目录vectorvecotor的介绍vector的模拟实现类的框架成员变量迭代器构造函数析构函数size()capacity()operator[]重载扩容resize()尾插验证是否为空尾删clear 清除swap交换insert插入erase删除迭代器区间初始化构造函数拷贝构造赋值运算符重载n个val构造函数再谈构造函数vect…

数仓日记 - 数仓理论

寒刃尽断处&#xff0c;吾心作剑霜作锋&#x1f3c2; 目录 一、数仓简介 二、关系建模与维度建模 1. 关系建模   2. 维度建模    • 三种模型    • 事实表    • 维度表   3. 事实表的分类    • 事务型事实表    • 周期型快照事实表    • 累积型快照事实表…

Python操作Excel表格

本文介绍如何通过轻量级、零依赖&#xff08;仅使用标准库&#xff09;的 pylightxl 库操作Excel表格。 官网&#xff1a;Welcome to pylightxl documentation — pylightxl 2019 documentation 目录 一、入门 1. 读写CSV文件 2. 读Excel文件 3. 获取工作表和单元格数据 3…

前端css实现特殊日期网页变灰功能

前端变灰效果在网页实际使用过程中使用的比较少&#xff0c;但有时候又缺一不可&#xff0c;一般在大型哀悼日或纪念日的时候使用&#xff0c;使用后的网站页面会变成灰色(黑白色)。 我们先看下各大网站是怎么实现的&#xff1a; 1.csdn实现方式 2.淘宝 3.人民网 4.京东 5.掘…

Unity【Multiplayer 多人在线】服务端、客户端通用架构的使用指南

文章目录&#x1f6a9; Import&#x1f680; protogen使用方法&#x1fa90; 客户端接口&#x1f308; 服务端接口&#x1f9ed; 数据处理&#x1f3a8; Example&#x1f6a9; Import 下载SKFramework框架&#xff0c;导入到Unity中&#xff1b; 在框架Package Manager中搜索并…

osgEarth示例分析——osgearth_colorfilter

前言 osgearth_colorfilter颜色过滤器示例。本示例中&#xff0c;主要展示了6种颜色过滤器的使用&#xff0c;分别是:HSLColorFilter、RGBColorFilter、CMYKColorFilter、BrightnessContrastColorFilter、GammaColorFilter、ChromaKeyColorFilter。 执行命令 // 一条命令是一…

Docker日常运维小技巧

一、故障定位 1、查看容器内部 https 请求响应时间 docker exec -t $(docker ps -f nameblog_web -q) curl -H X-Forwarded-Proto:https \-w %{time_total} -o /dev/null -s localhost 2、查看容器日志 docker logs --tail 50 --follow --timestamps mediawiki_web_1 3、删…

深圳SMT贴片行业MES系统解决方案~MES系统服务商~先达智控

随着我国工业的迅速发展&#xff0c;所有电子行业都离不开SMT贴片生产&#xff0c;SMT贴片生产是电子行业的至关重要的一道工业环节&#xff0c;我国作为一个工业制造大国&#xff0c;有着完备的SMT现代产业体系。SMT贴片领域是我国支柱性产业其一&#xff0c;SMT贴片产品涵盖工…

【JavaWeb开发-Servlet】day01-使用TomCat实现本地web部署

目录 1、准备java web开发环境 &#xff08;1&#xff09;下载javaJDK&#xff08;推荐使用JDK1.8&#xff0c;企业常用且稳定&#xff09; &#xff08;2&#xff09;下载TomCat服务器 2、创建web服务器TomCat (1)创建一个项目文件夹 (2)在文件夹中新建一个记事本并编以下…

算法大神左程云耗尽5年心血分享程序员代码面试指南第2版文档

前言 学习是一种基础性的能力。然而&#xff0c;“吾生也有涯&#xff0c;而知也无涯。”&#xff0c;如果学习不注意方法&#xff0c;则会“以有涯随无涯&#xff0c;殆矣”。 学习就像吃饭睡觉一样&#xff0c;是人的一种本能&#xff0c;人人都有学习的能力。我们在刚出生…

移动WEB开发之rem布局--苏宁首页案例制作(技术方案1)

案例&#xff1a;苏宁网移动端首页 访问地址&#xff1a;苏宁易购(Suning.com)-家电家装成套购&#xff0c;专注服务省心购&#xff01; 1. 技术选型 方案&#xff1a;我们采取单独制作移动页面方案 技术&#xff1a;布局采取rem适配布局&#xff08;less rem 媒体查询&am…

用 TensorFlow.js 在浏览器中训练一个计算机视觉模型(手写数字分类器)

文章目录Building a CNN in JavaScriptUsing Callbacks for VisualizationTraining with the MNIST DatasetRunning Inference on Images in TensorFlow.jsReferences我们在《在浏览器中运行 TensorFlow.js 来训练模型并给出预测结果&#xff08;Iris 数据集&#xff09;》中已…