Java并发之ReentrantReadWriteLock
上篇文章简单的介绍了ReentrantLock可重入锁。事实上我们可以理解可重入锁是一种排他锁,排他锁在同一个时刻只能够由一个线程进行访问。这就与我们实际使用过程中有点不想符合了,比如说当我们进行读写文件操作的时候,我们可能允许多个线程进行读文件操作,而对写文件只需要控制一个线程既可以。在这种业务情况下如果使用排他锁,可能不太符合而且效率也可能有些低下。
一、ReentrantReadWriteLock介绍
读写锁维护了一对锁,一个读锁和一个写锁。通过分离读锁和写锁使得并发性相比一般的排他锁在性能上有更好的一些优势。
二、ReentrantReadWriteLock的特性
公平性选择:支持公平锁和非公平锁。
可重入性:支持可重入性。例读线程获取了读锁以后可以继续获取读锁。写线程获取写锁以后可以继续获取写锁。
锁降级:遵循获取读锁,获取写锁在释放写锁的次序。支持写锁降级为读锁。
三、接口和API
读写锁ReentrantReadWriteLock实现接口ReadWriteLock。接口ReadWriteLock实现了两个方法。
即readLock()方法和writeLock()方法。
1 public interface ReadWriteLock {
2 Lock readLock();
3 Lock writeLock();
4 }
ReentrantReadWriteLock定义如下:
1 /** 内部类 读锁 */
2 private final ReentrantReadWriteLock.ReadLock readerLock;
3 /** 内部类 写锁 */
4 private final ReentrantReadWriteLock.WriteLock writerLock;
5 final Sync sync;
6 /** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */
7 public ReentrantReadWriteLock() {
8 this(false);
9 }
10 /** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */
11 public ReentrantReadWriteLock(boolean fair) {
12 sync = fair ? new FairSync() : new NonfairSync();
13 readerLock = new ReadLock(this);
14 writerLock = new WriteLock(this);
15 }
16 /** 返回用于写入操作的锁 */
17 public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
18 /** 返回用于读取操作的锁 */
19 public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
20 abstract static class Sync extends AbstractQueuedSynchronizer {
21
22 }
23 public static class WriteLock implements Lock, java.io.Serializable{
24
25 }
26 public static class ReadLock implements Lock, java.io.Serializable {
27
28 }
举例读写锁使用。使用读写锁将线程不安全的HashMap集合缓存变为线程安全的。
1 public class Cache {
2 static Map<String, Object> map = new HashMap<String, Object>(); static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock r = rwl.readLock();
3 static Lock w = rwl.writeLock();
4 // 获取一个key对应的value
5 public static final Object get(String key) {
6 r.lock(); try {
7 return map.get(key);
8 } finally {
9 r.unlock();
10 }
11 }
12 // 设置key对应的value,并返回旧的value
13 public static final Object put(String key, Object value) {
14 w.lock(); try {
15 return map.put(key, value);
16 } finally {
17 w.unlock();
18 }
19 }
20
21 // 清空所有的内容
22 public static final void clear() {
23 w.lock(); try {
24 map.clear();
25 } finally {
26 w.unlock();
27 }
28 }
29 }
四、读写锁的实现简单分析
在ReentrantLock中使用一个int类型的state来表示同步状态,该值表示锁被一个线程重复获取的次数。但是读写锁ReentrantReadWriteLock内部维护着一对锁,需要用一个变量维护多种状态。所以读写锁采用“按位切割使用”的方式来维护这个变量,将其切分为两部分,高16为表示读,低16为表示写。

当前同步状态表示一个线程已经获取了写锁,且重进入了两次,同时也连续获取了两次读锁。那么读写锁是如何迅速确定读锁和写锁的状态呢?通过为运算。假如当前同步状态为S,那么写状态等于 S & 0x0000FFFF(将高16位全部抹去),读状态等于S >>> 16(无符号补0右移16位)。
4.1、写锁的获取。写锁是一个可支持重入的排他锁。写锁的获取最终会调用tryAcquire(int arg)。注意:如果存在读锁,则写锁不能被获取,原因在于:读写锁要确保写锁的操作对读锁可见,如果允许读锁在已被获取的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当 前写线程的操作。
源码中该方法为以下代码:
1 protected final boolean tryAcquire(int acquires) {
2 Thread current = Thread.currentThread();
3 //当前锁个数
4 int c = getState();
5 //写锁
6 int w = exclusiveCount(c);
7 if (c != 0) {
8 //c != 0 && w == 0 表示存在读锁
9 //当前线程不是已经获取写锁的线程
10 if (w == 0 || current != getExclusiveOwnerThread())
11 return false;
12 //超出最大范围
13 if (w + exclusiveCount(acquires) > MAX_COUNT)
14 throw new Error("Maximum lock count exceeded");
15 setState(c + acquires);
16 return true;
17 }
18 //是否需要阻塞
19 if (writerShouldBlock() ||
20 !compareAndSetState(c, c + acquires))
21 return false;
22 //设置获取锁的线程为当前线程
23 setExclusiveOwnerThread(current);
24 return true;
25 }
4.2、写锁的释放:当写锁中写锁的状态值为0的时候。从而等待的线程这个时候可以获取读写锁了。
4.3、读锁的获取:读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问(或者写状态为0)时,读锁总会被成功地获取,而所做的也只是(线程安全的)增加读状态。如果当前线程已经获取了读锁,则增加读状态(依靠CAS保证线程安全)。如果当前线程在获取读锁时,写锁已被其他线程 获取,则进入等待状态。
4.4、读锁的释放:读锁的每次释放(线程安全的,可能有多个读线程同时释放读锁)均减少读状态,减少的值是(1<<16)。
五、锁降级
锁降级指的是写锁降级为读锁的情况。详细具体指的是当前拥有写锁的线程,在获取到读锁,然后在释放写锁的过程。注意:如果当前线程拥有写锁,然后将其释放,最后再获取读 锁,这种分段完成的过程不能称之为锁降级。
1 public void processData() {
2 readLock.lock();
3 if (!update) {
4 // 必须先释放读锁
5 readLock.unlock();
6 // 锁降级从写锁获取到开始
7 writeLock.lock();
8 try {
9 if (!update) {
10 // 准备数据的流程(略)
11 update = true;
12 }
13 readLock.lock();
14 } finally {
15 writeLock.unlock();
17 }
18 try {
19
20 }
21 // 锁降级完成,写锁降级为读锁
22 // 使用数据的流程(略)
23 } finally {
24 readLock.unlock();
25 }
26 }
锁降级中读锁的获取是否必要呢?答案是必要的。主要是为了保证数据的可见性,如果当前A线程不获取读锁而是直接释放写锁,假设此刻另一个线程B获取了写锁并修改了数据,那么当前A线程无法感知线程B的数据更新。如果当前线程A获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进 行数据更新。
RentrantReadWriteLock不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程)。目的也是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了 数据,则其更新对其他获取到读锁的线程是不可见的。
参考:
1、Java并发编程的艺术
2、Java并发编程网
Java并发之ReentrantReadWriteLock的更多相关文章
- Java并发之ReentrantReadWriteLock源码解析(一)
ReentrantReadWriteLock 前情提要:在学习本章前,需要先了解笔者先前讲解过的ReentrantLock源码解析和Semaphore源码解析,这两章介绍了很多方法都是本章的铺垫.下面 ...
- Java并发之ReentrantReadWriteLock源码解析(二)
先前,笔者和大家一起了解了ReentrantReadWriteLock的写锁实现,其实写锁本身实现的逻辑很少,基本上还是复用AQS内部的等待队列思想.下面,我们来看看ReentrantReadWrit ...
- java并发之固定对象与实例
java并发之固定对象与实例 Immutable Objects An object is considered immutable if its state cannot change after ...
- Java并发之BlockingQueue的使用
Java并发之BlockingQueue的使用 一.简介 前段时间看到有些朋友在网上发了一道面试题,题目的大意就是:有两个线程A,B, A线程每200ms就生成一个[0,100]之间的随机数, B线 ...
- 深入理解Java并发之synchronized实现原理
深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入 ...
- Java并发之Semaphore的使用
Java并发之Semaphore的使用 一.简介 今天突然发现,看着自己喜欢的球队发挥如此的棒,然后写着博客,这种感觉很爽.现在是半场时间,就趁着这个时间的空隙,说说Java并发包中另外一个重量级的类 ...
- Java并发之CyclicBarria的使用(二)
Java并发之CyclicBarria的使用(二) 一.简介 之前借助于其他大神写过一篇关于CyclicBarria用法的博文,但是内心总是感觉丝丝的愧疚,因为笔者喜欢原创,而不喜欢去转载一些其他的文 ...
- Java并发之CyclicBarria的使用
Java并发之CyclicBarria的使用 一.简介 笔者在写CountDownLatch这个类的时候,看到了博客园上的<浅析Java中CountDownLatch用法>这篇博文,为博主 ...
- Java并发之CountDownLatch的使用
Java并发之CountDownLatch的使用 一. 简介 Java的并发包早在JDK5这个版本中就已经推出,而且Java的并发编程是几乎每个Java程序员都无法绕开的屏障.笔者今晚在家闲来无事,翻 ...
随机推荐
- 深入 JAVA里面关于byte数组和String之间的转换问题
把byte转化成string,必须经过编码. 例如下面一个例子: importjava.io.UnsupportedEncodingException; publicclass test{ pub ...
- 学习pthreads,使用互斥量进行同步
在进行多线程编程时,我们总会遇到全局变量和数据结构的问题,这是多线程之间进行通信的问题.如果多个线程同时读写一个全局变量,那么会造成竞争或者出错.为了解决这一问题,我们需要对全局数据进行,使用互斥量实 ...
- 大型服装集团BI决策系统的分析主题模块
一般BI商业智能解决方案都包含财务.销售.客户等分析模块,本文分享的是某大型服装集团通过帆软FineBI建设的BI决策系统.该决策系统主要针对财务.资金.采购.生产.库存.物流.销售.渠道.产品.客户 ...
- VC工程的.gitignore模板
VC工程的.gitignore模板 文件内容如下: #====================================== # .gitignore # # 2015-01-09 create ...
- 网站开发进阶(八)tomcat异常日志分析及处理
tomcat异常日志分析及处理 日志信息如下: 2015-10-29 18:39:49 org.apache.coyote.http11.Http11Protocol pause 信息: Pausin ...
- Java中的50个关键字
form:http://blog.csdn.net/luoweifu/article/details/6776240 Java中的50个关键字 关键字也称为保留字,是指java语言中规定了特定含义的标 ...
- objective-c如何在linux下进入Modern模式
自从apple的obj-c进入2.0后,出现了相对于Legacy模式的Modern模式:Modern模式中出现了一些高级功能(比如ARC),并且出现了一些新的字面语法,新旧模式的差别可以参考apple ...
- obj-c编程15[Cocoa实例02]:KVC和KVO的实际运用
我们在第16和第17篇中分别介绍了obj-c的KVC与KVO特性,当时举的例子比较fun,太抽象,貌似和实际不沾边哦.那么下面我们就用一个实际中的例子来看看KVC与KVO是如何运用的吧. 该例中用到了 ...
- 一个简单的ruby生成器例子(用连续体Continuation实现)
ruby中有很多经典的驱动器结构,比如枚举器和生成器等.这次简单介绍下生成器的概念.生成器是按照功能要求,一次产生一个对象,或称之为生成一个对象的方法.ruby中的连续体正好可以用来完成生成器的功能. ...
- 遍历输出图片加hover
1. $(".icon a>div").hover(function () { var slls = $(this).attr("class"); sll ...