Java中的读写锁

一、读写锁

1、初识读写锁

  a)Java中的锁——Lock和synchronized中介绍的ReentrantLock和synchronized基本上都是排它锁,意味着这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,在写线程访问的时候其他的读线程和写线程都会被阻塞。读写锁维护一对锁(读锁和写锁),通过锁的分离,使得并发性提高。

  b)关于读写锁的基本使用:在不使用读写锁的时候,一般情况下我们需要使用synchronized搭配等待通知机制完成并发控制(写操作开始的时候,所有晚于写操作的读操作都会进入等待状态),只有写操作完成并通知后才会将等待的线程唤醒继续执行。

  如果改用读写锁实现,只需要在读操作的时候获取读锁,写操作的时候获取写锁。当写锁被获取到的时候,后续操作(读写)都会被阻塞,只有在写锁释放之后才会执行后续操作。并发包中对ReadWriteLock接口的实现类是ReentrantReadWriteLock,这个实现类具有下面三个特点

  ①具有与ReentrantLock类似的公平锁和非公平锁的实现:默认的支持非公平锁,对于二者而言,非公平锁的吞吐量由于公平锁;

  ②支持重入:读线程获取读锁之后能够再次获取读锁,写线程获取写锁之后能再次获取写锁,也可以获取读锁。

  ③锁能降级:遵循获取写锁、获取读锁在释放写锁的顺序,即写锁能够降级为读锁

2、读写锁源码分析

a)ReadWriteLock接口中只有两个方法,分别是readLock和writeLock

 1 public interface ReadWriteLock {
 2     /**
 3      * 返回读锁
 4      */
 5     Lock readLock();
 6 
 7     /**
 8      * 返回写锁
 9      */
10     Lock writeLock();
11 }

b)关于读写读写状态的设计

  ①作为已经实现的同步组件,读写锁同样是需要实现同步器来实现同步功能,同步器的同步状态就是读写锁的读写状态,只是读写锁的同步器需要在同步状态上维护多个读线程和写线程的状态。使用按位切割的方式将一个整形变量按照高低16位切割成两个部分。对比下图,低位值表示当前获取写锁的线程重入两次,高位的值表示当前获取读锁的线程重入一次。读写锁的获取伴随着读写状态值的更新。当低位为0000_0000_0000_0000的时候表示写锁已经释放,当高位为0000_0000_0000_0000的时候表示读锁已经释放。

  ②从下面的划分得到:当state值不等于0的时候,如果写状态(state & 0x0000FFFF)等于0的话,读状态是大于0的,表示读锁被获取;如果写状态不等于0的话,读锁没有被获取。这个特点也在源码中实现。

c)写锁writeLock

  ①上面说到过,读写锁是支持重入的锁,而对于写锁而言还是排他的,这样避免多个线程同时去修改临界资源导致程序出现错误。如果当前线程已经获取了写锁,则按照上面读写状态的设计增加写锁状态的值;如果当前线程在获取写锁的时候,读锁已经被获取或者该线程之前已经有别的线程获取到写锁,当前线程就会进入等待状态。

 1 static final int SHARED_SHIFT   = 16;
 2 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //1左移16位减1=>0000_0000_0000_0000_1111_1111_1111_1111
 3 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } //返回读状态的值
 4 protected final boolean tryAcquire(int acquires) {
 5         /*
 6          * Walkthrough:
 7          * 1. 如果读状态不为0或者写状态不为0并且写线程不是自己,返回false
 8          * 2. 如果已经超过了可重入的计数值MAX_COUNT,就会返回false
 9          * 3. 如果该线程是可重入获取或队列策略允许,则该线程有资格获得锁定;同时更新所有者和写锁状态值
10          */
11         Thread current = Thread.currentThread(); //获取当前线程
12         int c = getState(); //获取当前写锁状态值
13         int w = exclusiveCount(c); //获取写状态的值
14         //当同步状态state值不等于0的时候,如果写状态(state & 0x0000FFFF)等于0的话,读状态是大于0的,表示读锁被获取
15         if (c != 0) {
16             if (w == 0 || current != getExclusiveOwnerThread())
17                 return false;
18             if (w + exclusiveCount(acquires) > MAX_COUNT) //如果已经超过了可重入的计数值MAX_COUNT,就会返回false
19                 throw new Error("Maximum lock count exceeded");
20             // 重入锁:更新状态值
21             setState(c + acquires);
22             return true;
23         }
24         if (writerShouldBlock() ||
25             !compareAndSetState(c, c + acquires))
26             return false;
27         setExclusiveOwnerThread(current);
28         return true;
29     }

  ②分析一下上面的写锁获取源码

  tryAcquire中线程获取写锁的条件:读锁没有线程获取,写锁被获取并且被获取的线程是自己,那么该线程可以重入的获取锁,而判断读锁是否被获取的条件就是(当同步状态state值不等于0的时候,如果写状态(state & 0x0000FFFF)等于0的话,读状态是大于0的,表示读锁被获取)。对于读写锁而言,需要保证写锁的更新结果操作对读操作是可见的,这样的话写锁的获取就需要保证其他的读线程没有获取到读锁。

  ③写锁的释放源码

  写锁的释放和ReentrantLock的锁释放思路基本相同,从源码中可以看出来,每次释放都是减少写状态,直到写状态值为0(exclusiveCount(nextc) == 0)的时候释放写锁,后续阻塞等待的读写线程可以继续竞争锁。

 1 static final int SHARED_SHIFT   = 16;
 2 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //1左移16位减1=>0000_0000_0000_0000_1111_1111_1111_1111
 3 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } //返回读状态的值
 4 protected final boolean tryRelease(int releases) {
 5     if (!isHeldExclusively())
 6         throw new IllegalMonitorStateException();
 7     int nextc = getState() - releases;
 8     boolean free = exclusiveCount(nextc) == 0; //写状态值为0,就释放写锁,并将同步状态的线程持有者置为null,然后更新状态值
 9     if (free)
10         setExclusiveOwnerThread(null);
11     setState(nextc);
12     return free;
13 }

d)读锁readLock

  ①读锁是同样是支持重入的,除此之外也是共享式的,能够被多个线程获取。在同一时刻的竞争队列中,如果没有写线程想要获取读写锁,那么读锁总会被读线程获取到(然后更新读状态的值)。每个读线程都可以重入的获取读锁,而对应的获取次数保存在本地线程中,由线程自身维护该值。

  ②获取读锁的条件:其他线程已经获取了写锁,则当前线程获取读锁会失败而进入等待状态;如果当前线程获取了写锁或者写锁没有被获取,那么就可以获取到读锁,并更细同步状态(读状态值)。

  ③读锁的每次释放都是减少读状态,

f)锁的降级

  锁降级的概念:如果当先线程是写锁的持有者,并保持获得写锁的状态,同时又获取到读锁,然后释放写锁的过程。(注意不同于这样的分段过程:当前线程拥有写锁,释放掉写锁之后再获取读锁的过程,这种分段过程不能称为锁降级)。

 1 class CachedData {
 2   Object data;
 3   volatile boolean cacheValid;
 4   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 5 
 6   void processCachedData() {
 7     // 获取读锁
 8     rwl.readLock().lock();
 9     if (!cacheValid) {
10       // 在获取写锁之前必须释放读锁,不释放的话下面写锁会获取不成功,造成死锁
11       rwl.readLock().unlock();
12      // 获取写锁
13       rwl.writeLock().lock();
14       try {
15         // 重新检查state,因为在获取写锁之前其他线程可能已经获取写锁并且更改了state
16         if (!cacheValid) {
17           data = ...
18           cacheValid = true;
19         }
20         // 通过在释放写锁定之前获取读锁定来降级
21         // 这里再次获取读锁,如果不获取,那么当写锁释放后可能其他写线程再次获得写锁,导致下方`use(data)`时出现不一致的现象
22         // 这个操作就是降级
23         rwl.readLock().lock();
24       } finally {
25         rwl.writeLock().unlock(); // 释放写锁,由于在释放之前读锁已经被获取,所以现在是读锁获取状态
26       }
27     }
28 
29     try {
30     // 使用完后释放读锁
31       use(data);
32     } finally {
33       rwl.readLock().unlock(); //释放读锁
34     }
35   }
36  }}

 

转载于:https://www.cnblogs.com/fsmly/p/10721433.html

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

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

相关文章

Flex4中的皮肤(2): Skin State

在上一篇 中&#xff0c;定义了一个最简单的SkinnableComponent并为其定义了两个Skin。 对于TransitionSkin&#xff0c;需要在enable时有不同的展现方式&#xff0c;这可以通过Skin State实现。 对自定义的SkinnableComponent的修改 首先在组件中定义isEnabled属性&#xff1a…

休眠自动冲洗的黑暗面

介绍 既然我已经描述了JPA和Hibernate刷新策略的基础知识 &#xff0c;我就可以继续阐明Hibernate的AUTO刷新模式的令人惊讶的行为。 并非所有查询都会触发会话刷新 许多人会认为Hibernate 总是在执行任何查询之前先刷新Session。 虽然这可能是一种更直观的方法&#xff0c;并…

vue项目中z-index不起作用(将vue实例挂在到window上面)

问题描述&#xff1a;由于原有项目&#xff08;传统项目&#xff09;中嵌入新的vue组件&#xff0c;dialog弹出框的z-index&#xff1a;999999&#xff1b;任然不起作用&#xff1b; 解决办法&#xff1a;将vue实例挂载到window 解决代码如下&#xff1a; 入口文件index.js中 i…

IDE:5个最喜欢的NetBeans功能

愉快的发展……。 NetBeans具有许多有趣的功能 &#xff0c;这些功能使开发非常容易&#xff0c;只需很少的步骤&#xff0c;并且可以在非常快速地将产品推向市场的情况下提供高产的环境 。 将我的谈话仅限于五个功能非常困难&#xff0c;而此IDE具有大量有趣的功能。 但是在…

flask总结之session,websocket,上下文管理

1.关于session flask是带有session的&#xff0c;它加密后存储在用户浏览器的cookie中&#xff0c;可以通过app.seesion_interface源码查看 from flask import Flask,sessionapp Flask(__name__)app.secret_key aptx4869 # 必须要指定这个参数app.route(/login)def login():…

深入了解Oracle IDM审核

在处理敏感信息的任何产品中&#xff0c; 报告都是至关重要的功能。 同样适用于身份和访问管理工具。 Oracle IDM的审核模块是其OOTB报告功能的基础。 让我们快速看一下审核引擎以及它如何促进OIM中的报告功能。 这里展示的用例很简单– 在OIM中更改为用户记录。 从审核的角度…

django批量form表单处理

1.应用说明 一般在表单信息录入中&#xff0c;如果存在许多重复提交的信息&#xff0c;我们就需要进行批量处理&#xff0c;比如学生信息的批量录入。 这里一种方式就是使用xlrd模块处理&#xff0c;把学生信息录入到系统内 另外一种方式就是采用我们from组件中提供的formset来…

ADF:弹出窗口,对话框和输入组件

在本文中&#xff0c;当我们有一个af&#xff1a;popup包含af&#xff1a;dialog并在其中包含输入组件时&#xff0c;我想着重介绍一个非常常见的用例。 在实现此用例时&#xff0c;需要注意一些陷阱。 让我们考虑一个简单的示例&#xff1a; <af:popup id"p1" …

ORM 开发环境之利器:MVC 中间件 FreeSql.AdminLTE

前言 这是一篇纯技术干货的分享文章&#xff0c;FreeSql 已经基本完成 .NETCore 最方便的 ORM 使命&#xff0c;我们正在筹备生态的建立&#xff0c;比如 ABP 中如何使用 FreeSql 的实现&#xff0c;需要各种各样的扩展包&#xff0c;好多好多工作量。有没有大神愿意无偿参与做…

django中间件及中间件实现的登录验证

1.定义 一个用来处理Django的请求和响应的框架级别的钩子&#xff08;函数&#xff09;&#xff0c;相对比较轻量级&#xff0c;并且在全局上改变django的输入与输出&#xff08;使用需谨慎&#xff0c;否则影响性能&#xff09; 直白的说中间件就是帮助我们在视图函数执行之前…

二进制和十进制的相互转换

十进制转二进制&#xff1a; 方法一&#xff1a;y…… 25 * x 24 * x 23 * x 22 * x 21 * x 20 * x&#xff0c;其中y是十进制数字&#xff0c;x是0或1。 方法二&#xff1a; 二进制转十进制&#xff1a; 10100125 * 1 24 * 0 23 * 1 22 * 0 21 * 0 20 * 141 更多专业前端知…

Hadoop开发工具简介

几天前&#xff0c; Apache Hadoop开发工具 &#xff08;又名HDT &#xff09;发布了。 这些项目旨在将插件引入eclipse中&#xff0c;以简化Hadoop平台上的开发。 该博客旨在概述HDT的一些重要功能。 单端点 该项目可以充当HDFS&#xff0c;Zookeeper和MR群集的单个端点。 您…

使用Spring MVC时的常见错误

当我大约10年前开始我的职业生涯时&#xff0c;Struts MVC就是市场上的常态。 但是&#xff0c;多年来&#xff0c;我观察到Spring MVC逐渐流行起来。 鉴于Spring MVC与Spring容器的无缝集成以及它提供的灵活性和可扩展性&#xff0c;这对我来说并不奇怪。 从到目前为止的Spri…

C++ operator操作符重载(++,--,-,+,())

C中,--操作符重载需要说明是(--)在操作数前面,还是在操作数后面,区别如下: 代码经过测试无误(起码我这里没问题^_^)Code1#include <iostream> 2#include <cstdlib> 3using namespace std; 4template<typename T> class A 5{ 6public: 7 A(): m_(0){ 8 …

javax.el.PropertyNotFoundException: Property [Xxxx] not found on type Xxx.xxx.xxxx.Xxxx]的解决办法...

当我将后台数据传递给jsp&#xff0c;用${requestScope.user.Id}取值时报错&#xff0c; 最后发现entity实体类的属性不能首字母大写然后再小写&#xff0c;例如 int Age&#xff1b;这个就不行&#xff0c;必须写成int age; 最后问题解决了 转载于:https://www.cnblogs.com/Th…

初识C语言(五)

自定义函数 C语言提供了大量的库函数&#xff08;右侧资料下载中有&#xff09;&#xff0c;比如stdio.h提供输出函数&#xff0c;但是还是满足不了我们开发中的一些逻辑&#xff0c;所以这个时候需要自己定义函数&#xff0c;自定义函数的一般形式&#xff1a; 注意&#xff1…

nodeJS实现简单网页爬虫功能

前面的话 本文将使用nodeJS实现一个简单的网页爬虫功能 网页源码 使用http.get()方法获取网页源码&#xff0c;以hao123网站的头条页面为例 http://tuijian.hao123.com/hotrank var http require(http);http.get(http://tuijian.hao123.com/hotrank,function(res){var data ;…

JavaFX技巧6:使用透明颜色

为用户界面元素选择正确的颜色始终是一个很大的挑战&#xff0c;但是当您开发可重用的框架控件时&#xff0c;开发人员就无法控制使用它们的应用程序的外观和感觉&#xff0c;这甚至更具挑战性。 尽管您可能总是将元素添加到默认的灰色背景之上&#xff0c;但是嵌入控件的开发人…

38.QT-QAxObject快速写入EXCEL示例

参考链接: https://blog.csdn.net/czyt1988/article/details/52121360 http://blog.sina.com.cn/s/blog_a6fb6cc90101gv2p.html 1. QAxObject介绍 在QT中,有个自带的QAxObject类,可以直接操作EXCEL 除此之外,当我们操作某个文件夹下的EXCEL的时候,都会在该文件夹下出现一个隐藏…

EA常见画图(类图、包图、构件图、状态图、顺序图、活动图)

EA常见活动图&#xff0c;状态图画法 类图:111&#xff08;1&#xff09;给关系添加注释&#xff08;2&#xff09;设置关系线样式 包图&#xff1a;&#xff08;1&#xff09;创建包图&#xff08;2&#xff09;在包中添加子包&#xff1a;&#xff08;3&#xff09;在包中添加…