此处源码分析,主要是基于读锁,非公平机制,JDK1.8。

问题:

1、ReentrantReadWriteLock是如何创建读锁与写锁?

2、读锁与写锁的区别是什么?

3、锁的重入次数与获取锁的线程数分别是用哪种方式记录的?

4、当队列中出现多个共享模式的线程节点连续排列时,那么当第一个共享模式的线程拿到锁之后,后面的共享线程节点怎么获取锁?

一、创建ReadLock。

  1. ReentrantReadWriteLock rrw = new ReentrantReadWriteLock();
  1. public ReentrantReadWriteLock(boolean fair) {
  2. sync = fair ? new FairSync() : new NonfairSync();
  3. readerLock = new ReadLock(this);
  4. writerLock = new WriteLock(this);
  5. }
  1. rrw.readLock().lock();

1、当fair的值为false时,非公平的方式创建锁,当fair的值为true时,公平的方式创建锁。

2、初始化readerLock与writerLock,这两个变量是ReentrantReadWriteLock的内部变量。

3、sync执行非公平的锁。

二、lock()源码分析

2.1、sync.acquireShared(1)

  1. public void lock() {
  2. sync.acquireShared(1);
  3. }
  1. public final void acquireShared(int arg) {
  2. if (tryAcquireShared(arg) < 0)
  3. doAcquireShared(arg);
  4. }

(1)tryAcquireShared的作用是当前线程获取读锁,当返回1时,表示获取成功,-1表示获取失败。

(2)doAcquireShared,表示获取失败的时候调用。将获取失败的线程加入到等待队列中,并调用LockSupport.park方法阻塞住,等待线程释放permit。

2.2、tryAcquireShared(arg)

  1. protected final int tryAcquireShared(int unused) {
  2.  
  3. Thread current = Thread.currentThread();
  4. // 获取到占有锁的线程数
  5. int c = getState();
  6. // 如果写锁被占领了且不是当前线程占领,那么直接返回 -1
  7. if (exclusiveCount(c) != 0 &&
  8. getExclusiveOwnerThread() != current)
  9. return -1;
  10. int r = sharedCount(c); // 占有共享锁的线程数
  11. if (!readerShouldBlock() && // 如果队列的头节点的next节点是独享模式的线程节点即获取写锁的线程节点,返回true
  12. r < MAX_COUNT && // 共享的数据不能超过65535
  13. compareAndSetState(c, c + SHARED_UNIT)) { // cas设置state
  14. if (r == 0) { // 线程来拿读锁,读锁和写锁没有被任何线程拥有,那么r==0
  15. firstReader = current; //
  16. firstReaderHoldCount = 1;
  17. } else if (firstReader == current) { // 如果线程重复获取读锁,那么从这里开始重入
  18. firstReaderHoldCount++;
  19. } else { // 如果读锁被线程x占领,线程y也要来申请读锁,那么分支就走到这里了
  20.  
  21. // HoldCounter类中存储了两个属性,一个是count,用于记录线程的重入次数,一个是tid,记录当前线程的id
  22. HoldCounter rh = cachedHoldCounter;
  23.  
  24. // 线程x拥有读锁之后,线程y第一次申请的时候会走到这里
  25. //cachedHoldCounter 是一个缓存,保存当前操作线程的上一个线程的操作结果。线程y操作完之后,就会保存线程y的信息
  26. // 如果另外一个线程z来获取到读锁的时候,虽然rh!=null,但是rh.tid != getThreadId(current),
  27. //那么会创建一个默认的HoldCounter,并保存到cachedHoldCounter,并且默认的count=0
  28. if (rh == null || rh.tid != getThreadId(current))
  29. // readHolds.get(),查看源码可以知道,在这个方法中包含了数据初始化的过程,会调用ReentrantReadWriteLock.java
  30. // 下面的方法
  31. /**
  32. * public HoldCounter initialValue() {
  33. * return new HoldCounter();
  34. * }
  35. */
  36. cachedHoldCounter = rh = readHolds.get();
  37. else if (rh.count == 0) // 这个分支也会来到,当线程释放锁,但是没有关闭,当再次调用线程时,readHolds中会存在HoldCounter,count=0
  38. readHolds.set(rh);
  39. rh.count++; // 计算重入的次数
  40. }
  41. return 1;
  42. }
  43. return fullTryAcquireShared(current);
  44. }

请注意:

(1)ReentrantReadWriteLock中维持了一个类ThreadLocalHoldCounter,这个类会生成一个map,key是线程的id,value是HoldCounter对象,HoldCounter对象如下:

  1. static final class HoldCounter {
  2. int count = 0;
  3. // Use id, not reference, to avoid garbage retention
  4. final long tid = getThreadId(Thread.currentThread());
  5. }

其中count就是线程的重入次数,tid就是当前线程的id。这个是与ReentrantLock区别的地方。

(2)ReentrantReadWriteLock使用32位int类型来表示占有锁的线程数,其中高16位是获取到读锁的线程数,低16位是获取到写锁的线程数,提供了计算线程数的方法。

  1. static final int SHARED_SHIFT = 16;(1
  2. static final int SHARED_UNIT = (1 << SHARED_SHIFT);(2
  3. static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;(3
  4. static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;(4
  5.  
  6. /** Returns the number of shared holds represented in count */
  7. static int sharedCount(int c) { return c >>> SHARED_SHIFT; }(5
  8. /** Returns the number of exclusive holds represented in count */
  9. static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }(6
  10. c + SHARED_UNIT

其中(1)是共享移动常量;(2)是共享添加的常量;(3)是最大线程数65535(也就是11111111 11111111);(4)跟(3)一样;(5)计算共享线程数,把c的值向右移16为,并且高位补0; >>> 无符号右移,高位补0;(6)计算独享的线程数,把c的值与11111111 11111111 按位与,这样其实就是取到了写锁的线程数;(7)是共享线程+1。

源码分析:

2.2.1、readerShouldBlock()

这个方法的作用是把判断当前获取读锁的线程是否需要阻塞,条件是:在等待队列中头节点的下一个节点是独享模式的线程。

  1. // 读锁应该被阻塞
  2. final boolean readerShouldBlock() {
  3. return apparentlyFirstQueuedIsExclusive();
  4. }
  5.  
  6. /**
  7. * Returns {@code true} if the apparent first queued thread, if one
  8. * exists, is waiting in exclusive mode. If this method returns
  9. * {@code true}, and the current thread is attempting to acquire in
  10. * shared mode (that is, this method is invoked from {@link
  11. * #tryAcquireShared}) then it is guaranteed that the current thread
  12. * is not the first queued thread. Used only as a heuristic in
  13. * ReentrantReadWriteLock.
  14. *如果第一个入队列的线程节点存在,并且工作在独享模式下,那么返回true;
  15. *如果这个方法返回true,并且当前线程以共享的模式获取锁,这个方法保证了它不是第一个入队列的
  16. *(读锁与读锁都是共存的,所以不会入队,只有当队列中有独享模式的线程节点的时候,获取共享模式的线程才会加入到队列中。)
  17. */
  18. final boolean apparentlyFirstQueuedIsExclusive() {
  19. Node h, s;
  20. // 头节点存在,并且存在下一个节点,下一个节点是独享模式,下一个节点的thread不是空,则返回true
  21. return (h = head) != null &&
  22. (s = h.next) != null &&
  23. !s.isShared() &&
  24. s.thread != null;
  25. }

2.2.2、fullTryAcquireShared(current)

这个方法的作用与tryAcquireShared的作用很类似。

  1. // 进入这个方法的条件,
  2. /**条件1:!readerShouldBlock() && // 如果第一个入队列的线程节点存在,并且工作在独享模式下,那么返回true;
  3. * 条件2:r < MAX_COUNT && // 共享的数据不能超过65535,读锁的线程数已经超过了65535
  4. * 条件3:compareAndSetState(c, c + SHARED_UNIT) // 两个竞争读锁的线程都运行到这里,第一个竞争成功,那么第二个就会竞争失败,返回false
  5. * 其实这个方法分别对这三种状态进行处理
  6. */
  7. /**
  8. * Full version of acquire for reads, that handles CAS misses
  9. * and reentrant reads not dealt with in tryAcquireShared.
  10. */
  11. final int fullTryAcquireShared(Thread current) {
  12. /*
  13. * This code is in part redundant with that in
  14. * tryAcquireShared but is simpler overall by not
  15. * complicating tryAcquireShared with interactions between
  16. * retries and lazily reading hold counts.
  17. */
  18. HoldCounter rh = null;
  19. for (;;) {
  20. int c = getState();
  21. // 如果排他锁被别的线程拿了,直接返回-1
  22. if (exclusiveCount(c) != 0) {
  23. if (getExclusiveOwnerThread() != current)
  24. return -1;
  25. // else we hold the exclusive lock; blocking here
  26. // would cause deadlock.
  27. } else if (readerShouldBlock()) { // 这里是对条件1的处理
  28. // 如果队列的头的下一个节点是请求的排他锁的线程在等待,那么就返回true
  29. // Make sure we're not acquiring read lock reentrantly
  30. if (firstReader == current) {
  31. // assert firstReaderHoldCount > 0;
  32. } else {
  33. if (rh == null) {
  34. rh = cachedHoldCounter;
  35. if (rh == null || rh.tid != getThreadId(current)) {
  36. rh = readHolds.get();
  37. // 如果当前线程的count==0,也就是说当前线程才进来,没有获取到锁,那么直接把它从readHolds中移除
  38. if (rh.count == 0)
  39. // 移除当前线程的HoldCounter
  40. readHolds.remove();
  41. }
  42. }
  43. // 移除之后,返回-1
  44. if (rh.count == 0)
  45. return -1;
  46. }
  47. }
  48. // 这里是对条件2的处理,直接抛出错误!
  49. if (sharedCount(c) == MAX_COUNT)
  50. throw new Error("Maximum lock count exceeded");
  51. // 这里是对条件3的处理,竞争设置state,如果竞争还是失败,那么就要再循环一次,直到死循环能够跳出去
  52. if (compareAndSetState(c, c + SHARED_UNIT)) {
  53. // 如果共享锁的数量为0
  54. if (sharedCount(c) == 0) {
  55. // 设置第一个线程为当前的线程
  56. firstReader = current;
  57. // 设置HoldCount =1
  58. firstReaderHoldCount = 1;
  59. } else if (firstReader == current) {
  60. firstReaderHoldCount++;
  61. } else {
  62. if (rh == null)
  63. rh = cachedHoldCounter;
  64. if (rh == null || rh.tid != getThreadId(current))
  65. rh = readHolds.get();
  66. else if (rh.count == 0)
  67. readHolds.set(rh);
  68. rh.count++;
  69. cachedHoldCounter = rh; // cache for release
  70. }
  71. return 1;
  72. }
  73. }
  74. }

  

 

  

ReentrantReadWriteLock源码分析(一)的更多相关文章

  1. 【Java并发编程】16、ReentrantReadWriteLock源码分析

    一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...

  2. ReentrantReadWriteLock 源码分析

    ReentrantReadWriteLock  源码分析: 1:数据结构: 成员变量: private final ReentrantReadWriteLock.ReadLock readerLock ...

  3. Java并发指南10:Java 读写锁 ReentrantReadWriteLock 源码分析

    Java 读写锁 ReentrantReadWriteLock 源码分析 转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5 ...

  4. Java显式锁学习总结之五:ReentrantReadWriteLock源码分析

    概述 我们在介绍AbstractQueuedSynchronizer的时候介绍过,AQS支持独占式同步状态获取/释放.共享式同步状态获取/释放两种模式,对应的典型应用分别是ReentrantLock和 ...

  5. ReentrantReadWriteLock源码分析笔记

    ReentrantReadWriteLock包含两把锁,一是读锁ReadLock, 此乃共享锁, 一是写锁WriteLock, 此乃排它锁. 这两把锁都是基于AQS来实现的. 下面通过源码来看看Ree ...

  6. ReentrantReadWriteLock 源码分析以及 AQS 共享锁 (二)

    前言 上一篇讲解了 AQS 的独占锁部分(参看:ReentrantLock 源码分析以及 AQS (一)),这一篇将介绍 AQS 的共享锁,以及基于共享锁实现读写锁分离的 ReentrantReadW ...

  7. ReentrantReadWriteLock源码分析及理解

    本文结构 读写锁简介:介绍读写锁.读写锁的特性以及类定义信息 公平策略及Sync同步器:介绍读写锁提供的公平策略以及同步器源码分析 读锁:介绍读锁的一些常用操作和读锁的加锁.解锁的源码分析 写锁:介绍 ...

  8. 多线程之美7一ReentrantReadWriteLock源码分析

    目录 前言 在多线程环境下,为了保证线程安全, 我们通常会对共享资源加锁操作,我们常用Synchronized关键字或者ReentrantLock 来实现,这两者加锁方式都是排他锁,即同一时刻最多允许 ...

  9. Java并发编程笔记之读写锁 ReentrantReadWriteLock 源码分析

    我们知道在解决线程安全问题上使用 ReentrantLock 就可以,但是 ReentrantLock 是独占锁,同时只有一个线程可以获取该锁,而实际情况下会有写少读多的场景,显然 Reentrant ...

随机推荐

  1. win10如何安装和创建 证书

    .下载winsdksetup.exe .在 MMC 管理单元中查看证书 打开一个命令提示符窗口. 类型mmc然后按 ENTER 键. 请注意,若要查看本地计算机存储中的证书,您必须具有管理员角色. 上 ...

  2. 泛型约束where条件的使用(可以通过类型参数动态反射创建实例)

    定义抽象的人类 using System; using System.Collections.Generic; using System.Linq; using System.Text; using ...

  3. 4.spring对象的创建(静态工厂 实例工厂 泛型,嵌套类型)

    1.原料类 namespace CreateObjects{    public class GenericClass<T>    { }} PersonDao 类 包含嵌套类型 name ...

  4. SQLInjection 靶场配置

    对于渗透,太小型的网站没有太大价值,而大型网站(比如各种电商平台)对于代码审计往往非常严格,新手基本找不到漏洞,而一些比较容易搞掉的站点(政府.gov.各种教育网站.edu或者很多商业中型站点)渗透又 ...

  5. 二进制搭建kubernetes多master集群【开篇、集群环境和功能介绍】

    本文主要说明kubernetes集群使用组建的版本和功能介绍.. 一.组件版本 Kubernetes 1.12.3 Docker 18.06.1-ce Etcd 3.3.10 Flanneld 0.1 ...

  6. 27. Green Building 绿色建筑

    27. Green Building 绿色建筑 ①When we think of green buildings,we tend to think of new ones-the kind of h ...

  7. UVaLive 3487 Duopoly (最小割)

    题意:有两个公司A和B在申请一些资源,现在给出两个公司所申请的内容,内容包括价钱和申请的资源 ,现在你做为官方,你只能拒绝一个申请或者接受一个申请,同一个资源不能两个公司都拥有,且申请的资源不能只给部 ...

  8. Spring mvc,jQuery和JSON数据交互

    一.实验环境的搭建 1.Spring mvc jar. 导入spring mvc运行所需jar包.导入如下(有多余) 2.json的支持jar 3.加入jQuery. 选用jquery-3.0.0.m ...

  9. SetFocus (GetDlgItem (hwnd, idFocus))函数的各参数的具体含义

    Setfocus(HWMD hwnd):将窗口hwnd设置成获得焦点 GetDlgItem (hwnd, idFocus):此函数返回一个句柄 具体参数的含义: hwnd:包含该窗口标志位的对话框的句 ...

  10. 网页程序 vs 桌面程序

    网页程序 vs 桌面程序 阅读:  评论:  作者:Rybby  日期:  来源:rybby.com 所谓的网页程序就是指以网页作为程序的操作界面,通过脚本语言“javascript”或其它客户端语言 ...