Java并发包的locks包里的锁基本上已经介绍得差不多了,ReentrantLock重入锁是个关键,在清楚的了解了同步器AQS的运行机制后,实际上再分析这些锁就会显得容易得多,这章节主讲另外一个重要的锁——ReentrantReadWriteLock读写锁。

  ReentrantLock是一个独占锁,也就是说只能由一个线程获取锁,但如果场景是线程只做读的操作呢?这样ReentrantLock就不是很合适,读的线程并不需要保证其线程的安全性,任何一个线程都能去获取锁,只有这样才能尽可能地保证性能和效率。ReentrantReadWriteLock就是这样的一个锁,在其内部分为读锁和写锁,可以有N个读操作线程获取到写锁,但是只能有1个写操作线程获取到写锁,那么可以预见的是写锁是共享锁(AQS中的共享模式),读锁是独占锁(AQS中的独占模式)。首先来看读写锁的接口类:

 public interface ReadWriteLock {
Lock readLock(); //获取读锁
Lock writeLock(); //获取写锁
}

  可以看到ReadWriteLock接口只定义了两个方法,获取读锁和获取写锁的方法。下面是ReadWriteLock的实现类——ReentrantReadWriteLock。  

  和ReentrantLock类似,ReentrantReadWriteLock在其内部也是通过一个内部类Sync实现同步器AQS,同样也是通过实现Sync实现公平锁和非公平锁,这一点的思路和ReentrantLock类似。在ReadWriteLock接口中获取的读锁和写锁是怎么实现的呢?

//ReentrantReadWriteLock
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
public ReentrantReadWriteLock(){
this(false); //默认非公平锁
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync(); //锁类型(公平/非公平)
readerLock = new ReadLock(this); //构造读锁
writerLock = new WriteLock(this); //构造写锁
}
……
public ReentrantReadWriteLock.WriteLock writeLock0{return writerLock;}
public ReentrantReadWriteLock.ReadLock readLock0{return ReaderLock;}
//ReentrantReadWriteLock$ReadLock
public static class ReadLock implements Lock {
protected ReadLock(ReentrantReadwritLock lock) {
sync = lock.sync; //最后还是通过Sync内部类实现锁
  }
…… //它实现的是Lock接口,其余的实现可以和ReentrantLock作对比,获取锁、释放锁等等
}
//ReentrantReadWriteLock$WriteLock
public static class WriteLock implemnts Lock {
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
  }
…… //它实现的是Lock接口,其余的实现可以和ReentrantLock作对比,获取锁、释放锁等等
}

  上面是对ReentrantReadWriteLock做了一个大致的介绍,可以看到在其内部有好几个内部类,实际上读写锁内有两个锁——ReadLock、WriteLock,这两个锁都是实现自Lock接口,可以和ReentrantLock对比,而这两个锁的内部实现则是通过Sync,也就是同步器AQS实现的,这也可以和ReentrantLock中的Sync对比。
  回顾一下AQS,其内部有两个重要的数据结构——一个是同步队列、一个则是同步状态,这个同步状态应用到读写锁中也就是读写状态,但AQS中只有一个state整型来表示同步状态,读写锁中则有读、写两个同步状态需要记录。所以,读写锁将AQS中的state整型做了一下处理,它是一个int型变量一共4个字节32位,那么可以读写状态就可以各占16位——高16位表示读,低16位表示写。

  

  现在有一个疑问如果state的值位5,二进制为(00000000000000000000000000000101),如何快速确定读和写各自的状态呢?这就要用到位移运算了。计算方式为:写状态state & 0x0000FFFF,读状态state >>> 16。写状态增加1等于state + 1,读状态增加1等于state + (1 << 16)。有关移位运算可以参考《<<、>>、>>>移位操作》

写锁的获取与释放

  根据我们之前的经验可以得知:AQS已经将获取锁的算法骨架搭好了,只需子类实现tryAcquire(独占锁),故我们只需查看tryAcquire。

 //ReentrantReadWriteLock$Sync
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread;
int c = getState(); //获取state状态
int w = exclusiveCount(c); //获取写状态,即 state & 0x00001111
if (c != 0) { //存在同步状态(读或写),作下一步判断
if (w == 0 || current != getExclusiveOwnerThread()) //写状态为0,但同步状态不为0表示有读状态,此时获取锁失败,或者当前已经有其他写线程获取了锁此时也获取锁失败
return false;
if (w + exclusiveCount(acquire) > MAX_COUNT) //锁重入是否超过限制
throw new Error(“Maxium lock count exceeded”);
setState(c + acquire); //记录锁状态
return true;
  }
  if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
  return false; //writerShouldBlock对于非公平锁总是返回false,对于公平锁则判断同步队列中是否有前驱节点
  setExclusiveOwnerThread(current);
  return true;
}

  上面是写锁的状态获取,不好理解的是writerShouldBlock方法,此方法上面有描述,非公平锁直接返回false,而对于公平锁则是调用hasQueuedPredecessors方法如下:

 //ReentrantReadWriteLock$FairSync
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}

  原因是为什么呢?这就要回到非公平锁和公平锁的区别上来了,简单回顾一下,详情可参考《5.Lock接口及其实现ReentrantLock》。对于非公平锁,每次线程获取锁时首先会强行进行锁获取操作而不管同步队列中是否有线程,当获取不到时才会将线程构造至队尾;对于公平锁来讲,只要同步队列中存在线程,就不会去获取锁,而是将线程构造添加至队尾。所以重新回到写状态的获取上,tryAcquire方法里,前面发现没有线程持有锁,但是此时会根据锁的不同做相应操作,对于非公平锁——抢锁,对公平锁——同步队列中有线程,不抢锁,添加至队尾排队。
  写锁的释放与ReentrantLock的释放过程基本类似,毕竟都是独占锁,每次释放减少写的状态,直到减小到0就表示写锁已经完全释放。

读锁的获取与释放

  同理,根据我们之前的经验可以得知:AQS已经将获取锁的算法骨架搭好了,只需子类实现tryAcquireShared(共享锁),故我们只需查看tryAcquireShared。我们知道对于共享模式下的锁,它能够被多个线程同时获取,现在问题来了,T1线程获取了锁,同步状态state=1,此时T2也获取了锁,state=2,接着T1线程重入state=3,也就是说读状态是所有线程读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLock中,由线程自身维护,所以在这个地方要做一些复杂处理,源码有点长,但复杂就在于每个线程保存自身获取读锁的次数,具体参照源码的tryAcquireShared,仔细阅读并结合上面对写锁获取的分析不难读懂。
  读锁的释放值得注意的地方在于自身维护的获取锁的次数,以及通过移位操作减少状态state – (1 << 16)。

7.ReadWriteLock接口及其实现ReentrantReadWriteLock的更多相关文章

  1. Java并发ReadWriteLock接口

    java.util.concurrent.locks.ReadWriteLock接口允许一次读取多个线程,但一次只能写入一个线程. 读锁 - 如果没有线程锁定ReadWriteLock进行写入,则多线 ...

  2. ReadWriteLock 接口详解

    ReadWriteLock 接口详解 这是本人阅读ReadWriteLock接口源码的注释后,写出的一篇知识分享博客 读写锁的成分是什么? 读锁 Lock readLock(); 只要没有写锁,读锁可 ...

  3. Lock接口和ReadWriteLock接口

    Lock接口 Lock接口在java.util.concurrent.locks包中,在jdk1.5之后才有. Lock接口有6个方法: void lock(); void lockInterrupt ...

  4. 从源码来看ReentrantLock和ReentrantReadWriteLock

    上一篇花了点时间将同步器看了一下,心中对锁的概念更加明确了一点,知道我们所使用到的锁是怎么样获取同步状态的,我们也写了一个自定义同步组件Mutex,讲到了它其实就是一个简版的ReentrantLock ...

  5. 线程中的读写锁ReadWriteLock

    Lock锁还有两个非常强大的类 ReadWriteLock接口实现类ReentrantReadWriteLock(非常重要的锁) 想实现 读取的时候允许多线程并发访问,写入的时候不允许. 这种效果.. ...

  6. 源码分析:ReentrantReadWriteLock之读写锁

    简介 ReentrantReadWriteLock 从字面意思可以看出,是和重入.读写有关系的锁,实际上 ReentrantReadWriteLock 确实也是支持可重入的读写锁,并且支持公平和非公平 ...

  7. Synchronized和Lock接口

    关于synchronized字段,不管该关键字是修饰方法还是修饰同步代码块,synchronzed拿到的都是对象. 当synchronized修饰的是方法时,synchronized所拿到的是调用该方 ...

  8. 【转】Java并发编程:Lock

    阅读目录 一.synchronized的缺陷 二.java.util.concurrent.locks包下常用的类 三.锁的相关概念介绍 来自: http://www.importnew.com/18 ...

  9. Java并发编程:Lock

    原文出处: 海子 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包 ...

随机推荐

  1. 循环神经网络RNN公式推导走读

    0语言模型-N-Gram 语言模型就是给定句子前面部分,预测后面缺失部分 eg.我昨天上学迟到了,老师批评了____. N-Gram模型: ,对一句话切词 我 昨天 上学 迟到 了 ,老师 批评 了 ...

  2. 【割点】【割边】tarjan

    洛谷割点模板题--传送门 割边:在连通图中,删除了连通图的某条边后,图不再连通.这样的边被称为割边,也叫做桥.割点:在连通图中,删除了连通图的某个点以及与这个点相连的边后,图不再连通.这样的点被称为割 ...

  3. node-webkit学习之【无边框窗口用JS实现拖动改变大小等】

    效果如下图 原生的如下图(原生的用在自己的app上面太丑了,并且还带边框,所以重写了左上角的三个功能键) 1.首先了解一下nw底下的package.json 文件 { "name" ...

  4. JS前端数据格式化

    当我们从后台取了数据,但是我们希望在前台统一显示格式时,我们可能需要格式化数据. 今天正好总结一下前端JS格式化数据的几个方法: 1. toFixed() 方法   可把 Number 四舍五入为指定 ...

  5. DirectFB 之 简介

    1. DirectFB概述        首先 DirectFB 类似于桌面中的 XFree86 .桌面中的 XFree86 不需要 Frame Buffer 设备,而 DirectFB 需要.   ...

  6. hadoop高可靠性HA集群

    概述 简单hdfs高可用架构图 在hadoop2.x中通常由两个NameNode组成,一个处于active状态,另一个处于standby状态.Active NameNode对外提供服务,而Standb ...

  7. Android NDK开发之从Java与C互调中详解JNI使用(一)

    生活 这一个礼拜过得真的是苦不堪言,上周因为打球脚踝直接扭伤,肿的想猪蹄一样,然后休息几天消肿了,可以缓慢龟速的行走了,然而五一回来上班第一天,上班鞋子还能穿上,下班脚已插不进鞋子里面了,好吧,又肿回 ...

  8. openlayers应用“四”:百度地图纠偏续

    续前一篇,上一篇提到百度地图纠偏的基本思路,经过经过一天的努力,实现了百度地图坐标偏移参数的提取,步骤以及实现效果如下: 1.数据来源:四川省的省道矢量数据 2.提取坐标偏移参数的过程如下: A.将四 ...

  9. Java 7之基础 - 强引用、弱引用、软引用、虚引用

    1.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.如下: Object o=new Object();   //  强引用 当内 ...

  10. POJ1845-Sumdiv大数约数和

    题目链接:http://poj.org/problem?id=1845 题目大意: 求A^B的所有约数和s.A和B都很大(0<=A,B<=50000000). 题目分析: 这道题让我学会了 ...