读锁的调用,最终委派给其内部类 Sync extends AbstractQueuedSynchronizer
 /**
* 获取读锁,如果写锁不是由其他线程持有,则获取并立即返回;
* 如果写锁被其他线程持有,阻塞,直到读锁被获得。
*/
public void lock() {
sync.acquireShared(1);
}
/**
* 以共享模式获取对象,忽略中断。通过至少先调用一次 tryAcquireShared(int) 来实现此方法,并在成功时返回。
* 否则将线程加入队列,在阻塞和运行之间切换,重复调用tryAcquireShared直到成功
*( possibly repeatedly blocking and unblocking, invoking tryAcquireShared until success)
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
tryAcquireShared方法的逻辑:
整体思路如下,具体实现细节,请参考下面的源码分析

一 写锁被占用的情况

   1 写锁被其他线程占用,获取失败

   2 写锁被自己占用,表示“锁降级”,进入【循环cas取读锁】流程

二 写锁没有被占用的情况   
    1 根据读锁获取策略判断是否阻塞(readerShouldBlock方法)

        (1) 公平锁策略:如果当前线程不是同步队列中的第一个节点,则阻塞

        (2) 非公平锁策略:为了防止写线程饥饿,如果同步队列中的第一个节点是写线程,则阻塞当前线程。

    2 如果需要阻塞,分以下2种情况:

         (1)当前线程是读锁重入,则不需要阻塞,进入【循环cas取读锁】流程

         (2)当前线程不是读锁重入,则获取失败

3 如果不需要阻塞,进入【循环cas取读锁】流程
三  【循环cas取读锁】

    1 tryAcquireShared方法先进行了一次cas取锁操作,如果获取失败,则调用fullTryAcquireShared方法,循环获取。

    2 什么是cas?请参考CAS
 

protected final int tryAcquireShared(int unused) {

            Thread current = Thread.currentThread();
//c是锁状态为:高位16位表示共享锁的数量,低位16位表示独占锁的数量
int c = getState();
//exclusiveCount(c) 取低16位的值,也就是写锁状态位:不等于0表示写锁被占用
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
//写锁被其他线程占用,获取读锁失败
return -1; //取高16位的值,读锁状态位
int r = sharedCount(c);
//readerShouldBlock()根据读锁获取策略,返回是否阻塞当前读锁获取操作。后面会详细说明此方法
if (!readerShouldBlock() &&
r < MAX_COUNT &&
//cas修改高16位的读锁状态,即获取读锁
compareAndSetState(c, c + SHARED_UNIT)) { //首次获取读锁
if (r == 0) {
//缓存首次获取读锁的线程,及其读锁重入次数
firstReader = current;
firstReaderHoldCount = 1; } else if (firstReader == current) {
firstReaderHoldCount++; } else {
//cachedHoldCounter是最后获取锁的线程的读锁重入次数
HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current))
//readHolds是缓存了当前线程的读锁重入次数的ThreadLocal
//当前线程自然是最后获取锁的线程,故将当前线程的holdCounter赋给cachedHoldCounter
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
//缓存当前线程的holdCounter
//fullTryAcquireShared()方法中,
//获取读锁失败的线程会执行:readHolds.remove(),故此时需要重新设置
readHolds.set(rh); rh.count++;
}
return 1;
}
//首次获取读锁失败后,重试获取
return fullTryAcquireShared(current);
}
   /**
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in tryAcquireShared.
*/
final int fullTryAcquireShared(Thread current) {
//rh表示当前线程的锁计数器
HoldCounter rh = null;
for (;;) {
int c = getState(); //写锁被占用
if (exclusiveCount(c) != 0) {
//如果其他线程占用,读锁获取失败。如果当前线程占用,表示“锁降级”。
if (getExclusiveOwnerThread() != current)
return -1; } else if (readerShouldBlock()) {
//重入锁不需要阻塞。 // Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
//当前线程就是第一个获取读锁的线程,那么此时当然是重入锁。
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId()) {
rh = readHolds.get();
if (rh.count == 0)
//线程阻塞之前,清空readHolds
readHolds.remove();
}
}
if (rh.count == 0)
//当前线程的锁计数器为0,非重入锁,需要阻塞。
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded"); //cas设置读锁状态位
if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) {
//缓存首次获取读锁的线程,以及锁计数器
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
else if (rh.count == 0)
//锁计数器放入ThreadLocal
readHolds.set(rh); rh.count++;
//此时rh就是最后获取读锁的线程的锁计数器
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
  /**
* 非公平锁的读锁获取策略
*/
final boolean readerShouldBlock() {
//为了防止写线程饥饿
//如果同步队列中的第一个线程是以独占模式获取锁(写锁),
//那么当前获取读锁的线程需要阻塞,让队列中的第一个线程先执行
return apparentlyFirstQueuedIsExclusive();
} /**
* 公平锁的读锁获取策略
*/
final boolean readerShouldBlock() {
//如果当前线程不是同步队列头结点的next节点(head.next)
//则阻塞当前线程
return hasQueuedPredecessors();
}

java读写锁源码分析(ReentrantReadWriteLock)的更多相关文章

  1. java并发锁ReentrantReadWriteLock读写锁源码分析

    1.ReentrantReadWriterLock 基础 所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读 ...

  2. 多线程高并发编程(4) -- ReentrantReadWriteLock读写锁源码分析

    背景: ReentrantReadWriteLock把锁进行了细化,分为了写锁和读锁,即独占锁和共享锁.独占锁即当前所有线程只有一个可以成功获取到锁对资源进行修改操作,共享锁是可以一起对资源信息进行查 ...

  3. 细说并发5:Java 阻塞队列源码分析(下)

    上一篇 细说并发4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...

  4. Java split方法源码分析

    Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ...

  5. 【JAVA】ThreadLocal源码分析

    ThreadLocal内部是用一张哈希表来存储: static class ThreadLocalMap { static class Entry extends WeakReference<T ...

  6. 【Java】HashMap源码分析——常用方法详解

    上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...

  7. 【Java】HashMap源码分析——基本概念

    在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...

  8. Java并发包源码分析

    并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善.现代的PC都有多个CPU或一个CPU中有多个 ...

  9. Java - "JUC" Semaphore源码分析

    Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例 Semaphore简介 Semaphore是一个计数信号量,它的本质是一个"共享锁". 信号量维护了 ...

随机推荐

  1. APNs功能之Node.js和Mysql应用总结

    APNs功能之Node.js和Mysql应用总结 这篇文档主要是总结Node.js和Mysql的学习心得体会.当然也可以看作是此前所写的消息推送服务的续篇. 简单描述下应用背景,我们的应用需要实现苹果 ...

  2. Solrcloud,tomcat,外部zookeeper配置

  3. Unknown

    鉴于自己的直觉总是很准,所以这次再相信一次好了 T1:我觉得极有可能考到的是 1.对于栈,队列的模拟: 2.数论(不是像gcd那样的题目,而是加法原理乘法原理斥容,或是极具数学推导的东西,当然有可能用 ...

  4. WCF从零学习之WCF概述(一)

    WCF从零学习之WCF概述(一) 一.WCF概述 我先了解了分布式应用程序开发,所谓分布式应用程序是指应用程序分布在不同计算机上,通过网络来共同完成一项任务.通常为服务器/客户端模式. 在WCF发布之 ...

  5. 字符串拼接 strcat ;数组和指针的区别

    问题:字符串拼接 strcat 方法1: 开辟新空间,存放结果: #include <stdio.h> #include <stdlib.h> #include <str ...

  6. IOS学习之路十九(JSON与Arrays 或者 Dictionaries相互转换)

    今天写了个json与Arrays 或者 Dictionaries相互转换的例子很简单: 通过 NSJSONSerialization 这个类的 dataWithJSONObject: options: ...

  7. 7z文件格式及其源码

    7z文件格式及其源码的分析(四) 这是7z文件格式及其源码的分析系列的第四篇. 上一篇讲到了7z文件静态结构的尾header部分.这一篇开始,将从7z实际压缩流程开始详细介绍7z文件尾header的详 ...

  8. hive left outer join的问题

    最近BA用户反馈有两句看似很像的语句返回的结果数不一样,比较奇怪,怀疑是不是Hive的Bug Query 1 返回结果数6071 select count(distinct reviewid) as ...

  9. ibatis实战之一对多关联

    在实际开发中,我们常常遇到关联数据的情况,如User对象拥有若干Book对象 每个Book对象描述了归属于一个User信息,这种情况下,我们应该如何处理? 通过单独的Statement操作固然可以实现 ...

  10. JBPM4 安装和配置

    安装和配置jBPM4,并举个hello.w   至于网上很复杂,但又不懂的插件,我们先不鸟他. myeclipse8\dropins这里就是给我们放插件的,类似SVN之类的.所以小生也修改了下,共享了 ...