JUC学习之共享模型工具之JUC并发工具包上

x33g5p2x  于2022-01-09 转载在 其他  
字(38.5k)|赞(0)|评价(0)|浏览(418)

AQS 原理

概述

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

特点:

  • state 属性来表示资源的状态(分独占模式共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
    getState - 获取 state 状态

setState - 设置 state 状态

compareAndSetState - cas 机制设置 state 状态

独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源

  • 提供了基于 FIFO 的等待队列,类似于 MonitorEntryList
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 MonitorWaitSet

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

获取锁的姿势

  1. // 如果获取锁失败
  2. if (!tryAcquire(arg)) {
  3. // 入队, 可以选择阻塞当前线程 park unpark
  4. }

释放锁的姿势

  1. // 如果释放锁成功
  2. if (tryRelease(arg)) {
  3. // 让阻塞线程恢复运行
  4. }

实现不可重入锁

自定义同步器

  1. package schedule;
  2. import java.util.concurrent.locks.AbstractQueuedSynchronizer;
  3. import java.util.concurrent.locks.Condition;
  4. //自定义锁(不可重入锁)
  5. final class MySync extends AbstractQueuedSynchronizer {
  6. /** @param acquires 可重入数用来计数的,因为是不可重入锁,因此如果这里acquires的值大于一就返回false,表示加锁失败 */
  7. @Override
  8. protected boolean tryAcquire(int acquires) {
  9. if (acquires == 1) {
  10. //尝试加锁
  11. if (compareAndSetState(0, 1)) {
  12. //加上了锁,并设置owner为当前线程
  13. setExclusiveOwnerThread(Thread.currentThread());
  14. return true;
  15. }
  16. }
  17. //加锁失败
  18. return false;
  19. }
  20. /** @param acquires 可重入数用来计数的,因为是不可重入锁,因此如果这里acquires的值大于一就返回false,表示解锁失败 */
  21. @Override
  22. protected boolean tryRelease(int acquires) {
  23. if (acquires == 1) {
  24. //当前为没有上锁的状态
  25. if (getState() == 0) {
  26. throw new IllegalMonitorStateException();
  27. }
  28. //设置当前没有线程占用锁
  29. setExclusiveOwnerThread(null);
  30. //解锁--- private volatile int state
  31. //因为state是volatile,因此将setState(0);方法执行放在setExclusiveOwnerThread(null);方法执行之前可以确保
  32. //不会产生指令重排; 确保线程可见性;----exclusiveOwnerThread不是volatile
  33. setState(0);
  34. return true;
  35. }
  36. return false;
  37. }
  38. //条件变量
  39. protected Condition newCondition() {
  40. return new ConditionObject();
  41. }
  42. //是否持有独占锁--1:持有; 0不持有
  43. @Override
  44. protected boolean isHeldExclusively() {
  45. return getState() == 1;
  46. }
  47. }

同步器类中的大部分方法由其父类提供

自定义锁

有了自定义同步器,很容易复用 AQS ,实现一个功能完备的自定义锁

  1. package schedule;
  2. import java.util.concurrent.TimeUnit;
  3. import java.util.concurrent.locks.Condition;
  4. import java.util.concurrent.locks.Lock;
  5. class MyLock implements Lock {
  6. static MySync sync = new MySync();
  7. @Override
  8. // 尝试,不成功,进入等待队列
  9. public void lock() {
  10. sync.acquire(1);
  11. }
  12. @Override
  13. // 尝试,不成功,进入等待队列,可打断
  14. public void lockInterruptibly() throws InterruptedException {
  15. sync.acquireInterruptibly(1);
  16. }
  17. @Override
  18. // 尝试一次,不成功返回,不进入队列
  19. public boolean tryLock() {
  20. return sync.tryAcquire(1);
  21. }
  22. @Override
  23. // 尝试,不成功,进入等待队列,有时限
  24. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  25. return sync.tryAcquireNanos(1, unit.toNanos(time));
  26. }
  27. @Override
  28. // 释放锁
  29. public void unlock() {
  30. sync.release(1);
  31. }
  32. @Override
  33. // 生成条件变量
  34. public Condition newCondition() {
  35. return sync.newCondition();
  36. }
  37. }

release和tryRelease的区别:

测试一下

  1. MyLock lock = new MyLock();
  2. new Thread(() -> {
  3. lock.lock();
  4. try {
  5. log.debug("locking...");
  6. sleep(1);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. } finally {
  10. log.debug("unlocking...");
  11. lock.unlock();
  12. }
  13. }, "t1").start();
  14. new Thread(() -> {
  15. lock.lock();
  16. try {
  17. log.debug("locking...");
  18. } finally {
  19. log.debug("unlocking...");
  20. lock.unlock();
  21. }
  22. }, "t2").start();

输出

  1. 22:29:28.727 c.TestAqs [t1] - locking...
  2. 22:29:29.732 c.TestAqs [t1] - unlocking...
  3. 22:29:29.732 c.TestAqs [t2] - locking...
  4. 22:29:29.732 c.TestAqs [t2] - unlocking...

不可重入测试

如果改为下面代码,会发现自己也会被挡住(只会打印一次 locking)

  1. lock.lock();
  2. log.debug("locking...");
  3. lock.lock();
  4. log.debug("locking...");

心得

起源

早期程序员会自己通过一种同步器去实现另一种相近的同步器,例如用可重入锁去实现信号量,或反之。这显然不够优雅,于是在 JSR166(java 规范提案)中创建了 AQS,提供了这种通用的同步器机制。

目标

AQS 要实现的功能目标

  • 阻塞版本获取锁 acquire 和非阻塞的版本尝试获取锁 tryAcquire
  • 获取锁超时机制
  • 通过打断取消机制
  • 独占机制及共享机制
  • 条件不满足时的等待机制

要实现的性能目标
Instead, the primary performance goal here is scalability: topredictably maintain efficiency even, or especially, when
synchronizers are contended.

设计

AQS 的基本思想其实很简单

获取锁的逻辑

  1. while(state 状态不允许获取) {
  2. if(队列中还没有此线程) {
  3. 入队并阻塞
  4. }
  5. }
  6. 当前线程出队

释放锁的逻辑

  1. if(state 状态允许了) {
  2. 恢复阻塞的线程(s) }

要点

  • 原子维护 state 状态
  • 阻塞及恢复线程
  • 维护队列

1) state 设计

  • state 使用 volatile 配合 cas 保证其修改时的原子性
  • state 使用了 32bit int 来维护同步状态,因为当时使用 long 在很多平台下测试的结果并不理想

2) 阻塞恢复设计

  • 早期的控制线程暂停和恢复的 api 有 suspend 和 resume,但它们是不可用的,因为如果先调用的 resume 那么suspend 将感知不到
  • 解决方法是使用 park & unpark 来实现线程的暂停和恢复,具体原理在之前讲过了,先 unpark 再 park 也没问题
  • park & unpark 是针对线程的,而不是针对同步器的,因此控制粒度更为精细
  • park 线程还可以通过 interrupt 打断

3) 队列设计

使用了 FIFO 先入先出队列,并不支持优先级队列

设计时借鉴了 CLH 队列,它是一种单向无锁队列

队列中有 head 和 tail 两个指针节点,都用 volatile 修饰配合 cas 使用,每个节点有 state 维护节点状态

入队伪代码,只需要考虑 tail 赋值的原子性

  1. do {
  2. // 原来的 tail
  3. Node prev = tail;
  4. // 用 cas 在原来 tail 的基础上改为 node
  5. } while(tail.compareAndSet(prev, node))

出队伪代码

  1. // prev 是上一个节点
  2. while((Node prev=node.prev).state != 唤醒状态) {
  3. }
  4. // 设置头节点
  5. head = node;

CLH 好处:

  • 无锁,使用自旋
  • 快速,无阻塞

AQS 在一些方面改进了 CLH

  1. private Node enq ( final Node node){
  2. for (; ; ) {
  3. Node t = tail;
  4. // 队列中还没有元素 tail 为 null
  5. if (t == null) {
  6. // 将 head 从 null -> dummy
  7. if (compareAndSetHead(new Node()))
  8. tail = head;
  9. } else {
  10. // 将 node 的 prev 设置为原来的 tail
  11. node.prev = t;
  12. // 将 tail 从原来的 tail 设置为 node
  13. if (compareAndSetTail(t, node)) {
  14. // 原来 tail 的 next 设置为 node
  15. t.next = node;
  16. return t;
  17. }
  18. }
  19. }
  20. }

主要用到 AQS 的并发工具类

ReentrantLock 原理

非公平锁实现原理

加锁流程

先从构造器开始看,默认为非公平锁实现

  1. public ReentrantLock() {
  2. sync = new NonfairSync();
  3. }

NonfairSync 继承自 AQS

没有竞争,加锁成功的情况:

第一个竞争出现时

Thread-1 执行了

  1. CAS 尝试将 state 由 0 改为 1,结果失败
  2. 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败
  3. 接下来进入 addWaiter 逻辑,构造 Node 队列
  • 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态
  • Node 的创建是懒惰的
  • 其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程

当前线程进入 acquireQueued 逻辑
4. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
5. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
6. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false

  1. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时
    state 仍为 1,失败

8.当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回
true

9.进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

再次有多个线程经历上述过程竞争失败,变成这个样子

解锁流程

Thread-0 释放锁,进入 tryRelease 流程,如果成功

  • 设置 exclusiveOwnerThread 为 null
  • state = 0

  • 当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程—唤醒后继节点
  • 找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
  • 回到 Thread-1 的 acquireQueued 流程

如果加锁成功(没有竞争),会设置

  • exclusiveOwnerThread 为 Thread-1,state = 1
  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
  • 原本的 head 因为从链表断开,而可被垃圾回收

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

如果不巧又被 Thread-4 占了先

  • Thread-4 被设置为 exclusiveOwnerThread,state = 1
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

加锁流程源码分析:

加锁源码

  1. // Sync 继承自 AQS
  2. static final class NonfairSync extends Sync {
  3. private static final long serialVersionUID = 7316153563782823691L;
  4. // 加锁实现
  5. final void lock() {
  6. // 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁
  7. if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread());
  8. else
  9. // 如果尝试失败,进入 ㈠
  10. acquire(1);
  11. }
  12. // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
  13. public final void acquire(int arg) {
  14. // ㈡ tryAcquire
  15. if (!tryAcquire(arg) &&
  16. // 当 tryAcquire 返回为 false 时, 先调用 addWaiter ㈣, 接着 acquireQueued ㈤
  17. acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
  18. selfInterrupt();
  19. }
  20. }
  21. // ㈡ 进入 ㈢
  22. protected final boolean tryAcquire(int acquires) {
  23. return nonfairTryAcquire(acquires);
  24. }
  25. // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处
  26. final boolean nonfairTryAcquire(int acquires) {
  27. final Thread current = Thread.currentThread();
  28. int c = getState();
  29. // 如果还没有获得锁
  30. if (c == 0) {
  31. // 尝试用 cas 获得, 这里体现了非公平性: 不去检查 AQS 队列
  32. if (compareAndSetState(0, acquires)) {
  33. setExclusiveOwnerThread(current);
  34. return true;
  35. }
  36. }
  37. // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
  38. else if (current == getExclusiveOwnerThread()) {
  39. // state++
  40. int nextc = c + acquires;
  41. if (nextc < 0) // overflow
  42. throw new Error("Maximum lock count exceeded");
  43. setState(nextc);
  44. return true;
  45. }
  46. // 获取失败, 回到调用处
  47. return false;
  48. }
  49. // ㈣ AQS 继承过来的方法, 方便阅读, 放在此处
  50. private Node addWaiter(Node mode) {
  51. // 将当前线程关联到一个 Node 对象上, 模式为独占模式
  52. Node node = new Node(Thread.currentThread(), mode);
  53. // 如果 tail 不为 null, cas 尝试将 Node 对象加入 AQS 队列尾部
  54. Node pred = tail;
  55. if (pred != null) {
  56. node.prev = pred;
  57. if (compareAndSetTail(pred, node)) {
  58. // 双向链表
  59. pred.next = node;
  60. return node;
  61. }
  62. }
  63. // 尝试将 Node 加入 AQS, 进入 ㈥
  64. enq(node);
  65. return node;
  66. }
  67. // ㈥ AQS 继承过来的方法, 方便阅读, 放在此处
  68. private Node enq(final Node node) {
  69. for (; ; ) {
  70. Node t = tail;
  71. if (t == null) {
  72. // 还没有, 设置 head 为哨兵节点(不对应线程,状态为 0)
  73. if (compareAndSetHead(new Node())) {
  74. tail = head;
  75. }
  76. } else {
  77. // cas 尝试将 Node 对象加入 AQS 队列尾部
  78. node.prev = t;
  79. if (compareAndSetTail(t, node)) {
  80. t.next = node;
  81. return t;
  82. }
  83. }
  84. }
  85. }
  86. // ㈤ AQS 继承过来的方法, 方便阅读, 放在此处
  87. final boolean acquireQueued(final Node node, int arg) {
  88. boolean failed = true;
  89. try {
  90. boolean interrupted = false;
  91. for (; ; ) {
  92. final Node p = node.predecessor();
  93. // 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取
  94. if (p == head && tryAcquire(arg)) {
  95. // 获取成功, 设置自己(当前线程对应的 node)为 head
  96. setHead(node);
  97. // 上一个节点 help GC
  98. p.next = null;
  99. failed = false;
  100. // 返回中断标记 false
  101. return interrupted;
  102. }
  103. if (
  104. // 判断是否应当 park, 进入 ㈦
  105. shouldParkAfterFailedAcquire(p, node) &&
  106. // park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧
  107. parkAndCheckInterrupt()) {
  108. interrupted = true;
  109. }
  110. }
  111. } finally {
  112. if (failed) cancelAcquire(node);
  113. }
  114. }
  115. // ㈦ AQS 继承过来的方法, 方便阅读, 放在此处
  116. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  117. // 获取上一个节点的状态
  118. int ws = pred.waitStatus;
  119. if (ws == Node.SIGNAL) {
  120. // 上一个节点都在阻塞, 那么自己也阻塞好了
  121. return true;
  122. }
  123. // > 0 表示取消状态
  124. if (ws > 0) {
  125. // 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试
  126. do {
  127. node.prev = pred = pred.prev;
  128. } while (pred.waitStatus > 0);
  129. pred.next = node;
  130. } else {
  131. // 这次还没有阻塞
  132. // 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL
  133. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  134. }
  135. return false;
  136. }
  137. // ㈧ 阻塞当前线程
  138. private final boolean parkAndCheckInterrupt() {
  139. LockSupport.park(this);
  140. return Thread.interrupted();
  141. }
  142. }

注意

是否需要 unpark 是由当前节点的前驱节点的 waitStatus == Node.SIGNAL 来决定,而不是本节点的 waitStatus 决定

解锁流程

解锁不区分公平和非公平

被唤醒的线程,恢复到最初被park阻塞的位置:

源码:

  1. // Sync 继承自 AQS
  2. static final class NonfairSync extends Sync {
  3. // 解锁实现
  4. public void unlock() {
  5. sync.release(1);
  6. }
  7. // AQS 继承过来的方法, 方便阅读, 放在此处
  8. public final boolean release(int arg) {
  9. // 尝试释放锁, 进入 ㈠
  10. if (tryRelease(arg)) {
  11. // 队列头节点 unpark
  12. Node h = head;
  13. if (
  14. // 队列不为 null
  15. h != null &&
  16. // waitStatus == Node.SIGNAL 才需要 unpark
  17. h.waitStatus != 0) {
  18. // unpark AQS 中等待的线程, 进入 ㈡
  19. unparkSuccessor(h);
  20. }
  21. return true;
  22. }
  23. return false;
  24. }
  25. // ㈠ Sync 继承过来的方法, 方便阅读, 放在此处
  26. protected final boolean tryRelease(int releases) {
  27. // state--
  28. int c = getState() - releases;
  29. if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException();
  30. boolean free = false;
  31. // 支持锁重入, 只有 state 减为 0, 才释放成功
  32. if (c == 0) {
  33. free = true;
  34. setExclusiveOwnerThread(null);
  35. }
  36. setState(c);
  37. return free;
  38. }
  39. // ㈡ AQS 继承过来的方法, 方便阅读, 放在此处
  40. private void unparkSuccessor(Node node) {
  41. // 如果状态为 Node.SIGNAL 尝试重置状态为 0
  42. // 不成功也可以
  43. int ws = node.waitStatus;
  44. if (ws < 0) {
  45. compareAndSetWaitStatus(node, ws, 0);
  46. }
  47. // 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的
  48. Node s = node.next;
  49. // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点
  50. if (s == null || s.waitStatus > 0) {
  51. s = null;
  52. for (Node t = tail; t != null && t != node; t = t.prev)
  53. if (t.waitStatus <= 0) s = t;
  54. }
  55. if (s != null) LockSupport.unpark(s.thread);
  56. }
  57. }

可重入原理

  1. static final class NonfairSync extends Sync {
  2. // ...
  3. // Sync 继承过来的方法, 方便阅读, 放在此处
  4. final boolean nonfairTryAcquire(int acquires) {
  5. final Thread current = Thread.currentThread();
  6. int c = getState();
  7. if (c == 0) {
  8. if (compareAndSetState(0, acquires)) {
  9. setExclusiveOwnerThread(current);
  10. return true;
  11. }
  12. }
  13. // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
  14. else if (current == getExclusiveOwnerThread()) {
  15. // state++
  16. //如果此时c=1,表示当前线程已经拥有了这把锁,但是此时出现了锁重入的情况,state计数+1=2
  17. int nextc = c + acquires;
  18. if (nextc < 0) // overflow
  19. throw new Error("Maximum lock count exceeded");
  20. setState(nextc);
  21. return true;
  22. }
  23. return false;
  24. }
  25. // Sync 继承过来的方法, 方便阅读, 放在此处
  26. protected final boolean tryRelease(int releases) {
  27. // state--
  28. //当前线程state计数减去一
  29. int c = getState() - releases;
  30. //如果当前线程没有用有锁,还调用释放锁的方法会抛出异常
  31. if (Thread.currentThread() != getExclusiveOwnerThread())
  32. throw new IllegalMonitorStateException();
  33. //free变量标记是否释放了锁
  34. boolean free = false;
  35. // 支持锁重入, 只有 state 减为 0, 才释放成功
  36. if (c == 0) {
  37. free = true;
  38. setExclusiveOwnerThread(null);
  39. }
  40. setState(c);
  41. return free;
  42. }
  43. }

可打断原理

不可打断模式

在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

  1. // Sync 继承自 AQS
  2. static final class NonfairSync extends Sync {
  3. // ...
  4. private final boolean parkAndCheckInterrupt() {
  5. // 如果打断标记已经是 true, 则 park 会失效
  6. LockSupport.park(this);
  7. // interrupted 会清除打断标记
  8. return Thread.interrupted();
  9. }
  10. final boolean acquireQueued(final Node node, int arg) {
  11. boolean failed = true;
  12. try {
  13. boolean interrupted = false;
  14. for (; ; ) {
  15. final Node p = node.predecessor();
  16. if (p == head && tryAcquire(arg)) {
  17. setHead(node);
  18. p.next = null;
  19. failed = false;
  20. // 还是需要获得锁后, 才能返回打断状态
  21. return interrupted;
  22. }
  23. if (
  24. shouldParkAfterFailedAcquire(p, node) &&
  25. //parkAndCheckInterrupt方法在被唤醒或打断后,返回的是当前线程的打断标记,即打断状态
  26. //如果被打断了返回true,会进入if语句设置interrupted的值为true,再次尝试去获取锁
  27. parkAndCheckInterrupt()
  28. ) {
  29. // 如果是因为 interrupt 被唤醒, 返回打断状态为 true
  30. interrupted = true;
  31. }
  32. }
  33. } finally {
  34. if (failed)
  35. cancelAcquire(node);
  36. }
  37. }
  38. public final void acquire(int arg) {
  39. if (
  40. !tryAcquire(arg) &&
  41. acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
  42. ) {
  43. // 如果打断状态为 true
  44. selfInterrupt();
  45. }
  46. }
  47. static void selfInterrupt() {
  48. // 重新产生一次中断
  49. Thread.currentThread().interrupt();
  50. }
  51. }

可打断模式

  1. static final class NonfairSync extends Sync {
  2. public final void acquireInterruptibly(int arg) throws InterruptedException {
  3. if (Thread.interrupted())
  4. throw new InterruptedException();
  5. // 如果没有获得到锁, 进入 ㈠
  6. if (!tryAcquire(arg))
  7. doAcquireInterruptibly(arg);
  8. }
  9. // ㈠ 可打断的获取锁流程
  10. private void doAcquireInterruptibly(int arg) throws InterruptedException {
  11. final Node node = addWaiter(Node.EXCLUSIVE);
  12. boolean failed = true;
  13. try {
  14. for (; ; ) {
  15. final Node p = node.predecessor();
  16. if (p == head && tryAcquire(arg)) {
  17. setHead(node);
  18. p.next = null; // help GC
  19. failed = false;
  20. return;
  21. }
  22. if (shouldParkAfterFailedAcquire(p, node) &&
  23. // //parkAndCheckInterrupt方法在被唤醒或打断后,返回的是当前线程的打断标记,即打断状态
  24. //如果被打断了返回true,会进入if语句,然后抛出被打断的异常
  25. parkAndCheckInterrupt()) {
  26. // 在 park 过程中如果被 interrupt 会进入此
  27. // 这时候抛出异常, 而不会再次进入 for (;;)
  28. throw new InterruptedException();
  29. }
  30. }
  31. } finally {
  32. if (failed)
  33. cancelAcquire(node);
  34. }
  35. }
  36. }

公平锁实现原理

  1. static final class FairSync extends Sync {
  2. private static final long serialVersionUID = -3000897897090466540L;
  3. final void lock() {
  4. acquire(1);
  5. }
  6. // AQS 继承过来的方法, 方便阅读, 放在此处
  7. public final void acquire(int arg) {
  8. if (
  9. !tryAcquire(arg) &&
  10. acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
  11. ) {
  12. selfInterrupt();
  13. }
  14. }
  15. // 与非公平锁主要区别在于 tryAcquire 方法的实现
  16. protected final boolean tryAcquire(int acquires) {
  17. final Thread current = Thread.currentThread();
  18. int c = getState();
  19. if (c == 0) {
  20. // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
  21. if (!hasQueuedPredecessors() &&
  22. compareAndSetState(0, acquires)) {
  23. setExclusiveOwnerThread(current);
  24. return true;
  25. }
  26. } else if (current == getExclusiveOwnerThread()) {
  27. int nextc = c + acquires;
  28. if (nextc < 0)
  29. throw new Error("Maximum lock count exceeded");
  30. setState(nextc);
  31. return true;
  32. }
  33. return false;
  34. }
  35. // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
  36. public final boolean hasQueuedPredecessors() {
  37. Node t = tail;
  38. Node h = head;
  39. Node s;
  40. // h != t 时表示队列中有 Node
  41. return h != t &&
  42. (
  43. // (s = h.next) == null 表示队列中还有没有老二
  44. //头结点是哑元,老二节点优先级最高
  45. (s = h.next) == null ||
  46. // 或者队列中老二线程不是此线程
  47. s.thread != Thread.currentThread()
  48. );
  49. }
  50. }

对比非公平锁的实现

  1. // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处
  2. final boolean nonfairTryAcquire(int acquires) {
  3. final Thread current = Thread.currentThread();
  4. int c = getState();
  5. // 如果还没有获得锁
  6. if (c == 0) {
  7. // 尝试用 cas 获得, 这里体现了非公平性: 不去检查 AQS 队列
  8. if (compareAndSetState(0, acquires)) {
  9. setExclusiveOwnerThread(current);
  10. return true;
  11. }
  12. }
  13. // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
  14. else if (current == getExclusiveOwnerThread()) {
  15. // state++
  16. int nextc = c + acquires;
  17. if (nextc < 0) // overflow
  18. throw new Error("Maximum lock count exceeded");
  19. setState(nextc);
  20. return true;
  21. }
  22. // 获取失败, 回到调用处
  23. return false;
  24. }

条件变量实现原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject

await 流程

开始 Thread-0 持有锁,调用 await,进入 ConditionObjectaddConditionWaiter 流程

创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部

await()对应源码

  1. public final void await() throws InterruptedException {
  2. if (Thread.interrupted())
  3. throw new InterruptedException();
  4. //创建新的Node状态为-2(Node.CONDITION),关联 Thread-0,加入等待队列尾部
  5. Node node = addConditionWaiter();
  6. .....
  7. }

addConditionWaiter()对应源码

  1. //将等待者加入等待队列,返回新创建的Node等待节点
  2. private Node addConditionWaiter()
  3. {
  4. Node t = lastWaiter;
  5. // If lastWaiter is cancelled, clean out.
  6. if (t != null && t.waitStatus != Node.CONDITION) {
  7. unlinkCancelledWaiters();
  8. t = lastWaiter;
  9. }
  10. //新创建的节点关联当前线程,值为-2(Node.CONDITION值为-2)
  11. Node node = new Node(Thread.currentThread(), Node.CONDITION);
  12. //如果等待队列没有节点,那么当前新创建出来的节点作为头部
  13. //否则作为队列尾部节点
  14. if (t == null)
  15. firstWaiter = node;
  16. else
  17. t.nextWaiter = node;
  18. lastWaiter = node;
  19. return node;
  20. }

接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁

unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功

await()对应源码

  1. public final void await() throws InterruptedException {
  2. if (Thread.interrupted())
  3. throw new InterruptedException();
  4. //创建新的Node状态为-2(Node.CONDITION),关联 Thread-0,加入等待队列尾部
  5. Node node = addConditionWaiter();
  6. //进入fullyRelease(node)释放锁的流程
  7. int savedState = fullyRelease(node);
  8. ....
  9. }

fullyRelease(node)对应源码

  1. final int fullyRelease(Node node) {
  2. boolean failed = true;
  3. try {
  4. //获取当前state的值
  5. int savedState = getState();
  6. //释放锁,state值清零,即释放所有锁(可重入锁state值大于一)
  7. if (release(savedState)) {
  8. failed = false;
  9. return savedState;
  10. } else {
  11. throw new IllegalMonitorStateException();
  12. }
  13. } finally {
  14. if (failed)
  15. node.waitStatus = Node.CANCELLED;
  16. }
  17. }

release(savedState)对应源码

  1. public final boolean release(int arg)
  2. {
  3. //释放锁成功
  4. if (tryRelease(arg))
  5. {
  6. Node h = head;
  7. //头结点不为空,并且waitStatus 为-1
  8. if (h != null && h.waitStatus != 0)
  9. //唤醒头结点的后继节点--unpark
  10. unparkSuccessor(h);
  11. return true;
  12. }
  13. return false;
  14. }

tryRelease(arg)对应源码

  1. protected final boolean tryRelease(int releases)
  2. {
  3. //这里releases的值为int savedState = getState();
  4. //因此这里c为0,相当于释放了当前线程加的所有锁(可重入锁)
  5. int c = getState() - releases;
  6. if (Thread.currentThread() != getExclusiveOwnerThread())
  7. throw new IllegalMonitorStateException();
  8. boolean free = false;
  9. //锁被释放了
  10. if (c == 0)
  11. {
  12. free = true;
  13. //设置owner值为null
  14. setExclusiveOwnerThread(null);
  15. }
  16. //解锁
  17. setState(c);
  18. return free;
  19. }

park 阻塞 Thread-0

await()对应源码

  1. public final void await() throws InterruptedException {
  2. if (Thread.interrupted())
  3. throw new InterruptedException();
  4. //创建新的Node状态为-2(Node.CONDITION),关联 Thread-0,加入等待队列尾部
  5. Node node = addConditionWaiter();
  6. //进入fullyRelease(node)释放锁的流程
  7. int savedState = fullyRelease(node);
  8. int interruptMode = 0;
  9. while (!isOnSyncQueue(node)) {
  10. //调用park(this)方法,将自己阻塞,进入休息室等待
  11. LockSupport.park(this);
  12. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
  13. break;
  14. }
  15. ...
  16. }

signal 流程

假设 Thread-1 要来唤醒 Thread-0

signal()源码

  1. public final void signal() {
  2. //当前线程是否是锁的持有者
  3. if (!isHeldExclusively())
  4. throw new IllegalMonitorStateException();
  5. //获取等待队列中第一个节点
  6. Node first = firstWaiter;
  7. if (first != null)
  8. //如果第一个节点不为空,就唤醒第一个节点
  9. //这里可以看出,底层不是随机唤醒
  10. doSignal(first);
  11. }

进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node

执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的
waitStatus 改为 -1

Thread-1 释放锁,进入 unlock 流程,略
doSignal()源码

  1. private void doSignal(Node first) {
  2. do {
  3. //将当前等待队列中的第一个节点,从等待队列的双向链表中断开连接
  4. //如果第一个节点有后继节点,那么让后继节点成为第一个节点
  5. if ( (firstWaiter = first.nextWaiter) == null)
  6. //如果没有后继节点,说明当前队列就一个元素,lastWaiter 指针值变为null
  7. lastWaiter = null;
  8. //从链表中断开连接
  9. first.nextWaiter = null;
  10. //transferForSignal尝试将被唤醒的节点,加入AQS阻塞队列尾部
  11. //这里可能会操作失败,因为在加入队列过程中,存在被打断,取消的可能
  12. } while (!transferForSignal(first) &&
  13. //如果加入队列尾部失败,就再尝试去唤醒等待队列中下一个节点
  14. (first = firstWaiter) != null);
  15. }

transferForSignal(first)源码

  1. /** * 将当前节点从等待队列转移到同步队列 * 转移成功返回true * 如果转移过程中当前节点被取消了,那么转移失败 */
  2. final boolean transferForSignal(Node node) {
  3. /* * 将当前节点的状态由-2切换为0,如果切换失败,表示当前节点被取消了 */
  4. if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
  5. return false;
  6. /* * 将被唤醒的节点加入同步队列的尾部,如果加入成功,返回的是加入成功后该节点的前驱节点 */
  7. Node p = enq(node);
  8. //获取前驱节点的状态
  9. int ws = p.waitStatus;
  10. //设置前驱节点的状态为-1,表示他有职责唤醒他的后继节点
  11. if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
  12. //如果转移失败了,会唤醒该节点的线程
  13. LockSupport.unpark(node.thread);
  14. return true;
  15. }

ConditionObject 类源码

  1. public class ConditionObject implements Condition, java.io.Serializable {
  2. private static final long serialVersionUID = 1173984872572414699L;
  3. // 第一个等待节点
  4. private transient Node firstWaiter;
  5. // 最后一个等待节点
  6. private transient Node lastWaiter;
  7. public ConditionObject() {
  8. }
  9. // ㈠ 添加一个 Node 至等待队列
  10. private Node addConditionWaiter() {
  11. Node t = lastWaiter;
  12. // 所有已取消的 Node 从队列链表删除, 见 ㈡
  13. if (t != null && t.waitStatus != Node.CONDITION) {
  14. unlinkCancelledWaiters();
  15. t = lastWaiter;
  16. }
  17. // 创建一个关联当前线程的新 Node, 添加至队列尾部
  18. Node node = new Node(Thread.currentThread(), Node.CONDITION);
  19. if (t == null)
  20. firstWaiter = node;
  21. else
  22. t.nextWaiter = node;
  23. lastWaiter = node;
  24. return node;
  25. }
  26. // 唤醒 - 将没取消的第一个节点转移至 AQS 队列
  27. private void doSignal(Node first) {
  28. do {
  29. // 已经是尾节点了
  30. if ((firstWaiter = first.nextWaiter) == null) {
  31. lastWaiter = null;
  32. }
  33. first.nextWaiter = null;
  34. } while (
  35. // 将等待队列中的 Node 转移至 AQS 队列, 不成功且还有节点则继续循环 ㈢
  36. !transferForSignal(first) &&
  37. // 队列还有节点
  38. (first = firstWaiter) != null
  39. );
  40. }
  41. // 外部类方法, 方便阅读, 放在此处
  42. // ㈢ 如果节点状态是取消, 返回 false 表示转移失败, 否则转移成功
  43. final boolean transferForSignal(Node node) {
  44. // 如果状态已经不是 Node.CONDITION, 说明被取消了
  45. if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
  46. return false;
  47. // 加入 AQS 队列尾部
  48. Node p = enq(node);
  49. int ws = p.waitStatus;
  50. if (
  51. // 上一个节点被取消
  52. ws > 0 ||
  53. // 上一个节点不能设置状态为 Node.SIGNAL
  54. !compareAndSetWaitStatus(p, ws, Node.SIGNAL)
  55. ) {
  56. // unpark 取消阻塞, 让线程重新同步状态
  57. LockSupport.unpark(node.thread);
  58. }
  59. return true;
  60. }
  61. // 全部唤醒 - 等待队列的所有节点转移至 AQS 队列
  62. private void doSignalAll(Node first) {
  63. lastWaiter = firstWaiter = null;
  64. do {
  65. Node next = first.nextWaiter;
  66. first.nextWaiter = null;
  67. transferForSignal(first);
  68. first = next;
  69. } while (first != null);
  70. }
  71. // ㈡
  72. private void unlinkCancelledWaiters() {
  73. // ...
  74. }
  75. // 唤醒 - 必须持有锁才能唤醒, 因此 doSignal 内无需考虑加锁
  76. public final void signal() {
  77. if (!isHeldExclusively())
  78. throw new IllegalMonitorStateException();
  79. Node first = firstWaiter;
  80. if (first != null)
  81. doSignal(first);
  82. }
  83. // 全部唤醒 - 必须持有锁才能唤醒, 因此 doSignalAll 内无需考虑加锁
  84. public final void signalAll() {
  85. if (!isHeldExclusively())
  86. throw new IllegalMonitorStateException();
  87. Node first = firstWaiter;
  88. if (first != null)
  89. doSignalAll(first);
  90. }
  91. // 不可打断等待 - 直到被唤醒
  92. public final void awaitUninterruptibly() {
  93. // 添加一个 Node 至等待队列, 见 ㈠
  94. Node node = addConditionWaiter();
  95. // 释放节点持有的锁, 见 ㈣
  96. int savedState = fullyRelease(node);
  97. boolean interrupted = false;
  98. // 如果该节点还没有转移至 AQS 队列, 阻塞
  99. while (!isOnSyncQueue(node)) {
  100. // park 阻塞
  101. LockSupport.park(this);
  102. // 如果被打断, 仅设置打断状态
  103. if (Thread.interrupted())
  104. interrupted = true;
  105. }
  106. // 唤醒后, 尝试竞争锁, 如果失败进入 AQS 队列
  107. if (acquireQueued(node, savedState) || interrupted)
  108. selfInterrupt();
  109. }
  110. // 外部类方法, 方便阅读, 放在此处
  111. // ㈣ 因为某线程可能重入,需要将 state 全部释放
  112. final int fullyRelease(Node node) {
  113. boolean failed = true;
  114. try {
  115. int savedState = getState();
  116. if (release(savedState)) {
  117. failed = false;
  118. return savedState;
  119. } else {
  120. throw new IllegalMonitorStateException();
  121. }
  122. } finally {
  123. if (failed)
  124. node.waitStatus = Node.CANCELLED;
  125. }
  126. }
  127. // 打断模式 - 在退出等待时重新设置打断状态
  128. private static final int REINTERRUPT = 1;
  129. // 打断模式 - 在退出等待时抛出异常
  130. private static final int THROW_IE = -1;
  131. // 判断打断模式
  132. private int checkInterruptWhileWaiting(Node node) {
  133. return Thread.interrupted() ?
  134. (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
  135. 0;
  136. }
  137. // ㈤ 应用打断模式
  138. private void reportInterruptAfterWait(int interruptMode)
  139. throws InterruptedException {
  140. if (interruptMode == THROW_IE)
  141. throw new InterruptedException();
  142. else if (interruptMode == REINTERRUPT)
  143. selfInterrupt();
  144. }
  145. // 等待 - 直到被唤醒或打断
  146. public final void await() throws InterruptedException {
  147. if (Thread.interrupted()) {
  148. throw new InterruptedException();
  149. }
  150. // 添加一个 Node 至等待队列, 见 ㈠
  151. Node node = addConditionWaiter();
  152. // 释放节点持有的锁
  153. int savedState = fullyRelease(node);
  154. int interruptMode = 0;
  155. // 如果该节点还没有转移至 AQS 队列, 阻塞
  156. while (!isOnSyncQueue(node)) {
  157. // park 阻塞
  158. LockSupport.park(this);
  159. // 如果被打断, 退出等待队列
  160. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
  161. break;
  162. }
  163. // 退出等待队列后, 还需要获得 AQS 队列的锁
  164. if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
  165. interruptMode = REINTERRUPT;
  166. // 所有已取消的 Node 从队列链表删除, 见 ㈡
  167. if (node.nextWaiter != null)
  168. unlinkCancelledWaiters();
  169. // 应用打断模式, 见 ㈤
  170. if (interruptMode != 0)
  171. reportInterruptAfterWait(interruptMode);
  172. }
  173. // 等待 - 直到被唤醒或打断或超时
  174. public final long awaitNanos(long nanosTimeout) throws InterruptedException {
  175. if (Thread.interrupted()) {
  176. throw new InterruptedException();
  177. }
  178. // 添加一个 Node 至等待队列, 见 ㈠
  179. Node node = addConditionWaiter();
  180. // 释放节点持有的锁
  181. int savedState = fullyRelease(node);
  182. // 获得最后期限
  183. final long deadline = System.nanoTime() + nanosTimeout;
  184. int interruptMode = 0;
  185. // 如果该节点还没有转移至 AQS 队列, 阻塞
  186. while (!isOnSyncQueue(node)) {
  187. // 已超时, 退出等待队列
  188. if (nanosTimeout <= 0L) {
  189. transferAfterCancelledWait(node);
  190. break;
  191. }
  192. // park 阻塞一定时间, spinForTimeoutThreshold 为 1000 ns
  193. if (nanosTimeout >= spinForTimeoutThreshold)
  194. LockSupport.parkNanos(this, nanosTimeout);
  195. // 如果被打断, 退出等待队列
  196. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
  197. break;
  198. nanosTimeout = deadline - System.nanoTime();
  199. }
  200. // 退出等待队列后, 还需要获得 AQS 队列的锁
  201. if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
  202. interruptMode = REINTERRUPT;
  203. // 所有已取消的 Node 从队列链表删除, 见 ㈡
  204. if (node.nextWaiter != null)
  205. unlinkCancelledWaiters();
  206. // 应用打断模式, 见 ㈤
  207. if (interruptMode != 0)
  208. reportInterruptAfterWait(interruptMode);
  209. return deadline - System.nanoTime();
  210. }
  211. // 等待 - 直到被唤醒或打断或超时, 逻辑类似于 awaitNanos
  212. public final boolean awaitUntil(Date deadline) throws InterruptedException {
  213. // ...
  214. }
  215. // 等待 - 直到被唤醒或打断或超时, 逻辑类似于 awaitNanos
  216. public final boolean await(long time, TimeUnit unit) throws InterruptedException {
  217. // ...
  218. }
  219. // 工具方法 省略 ...
  220. }

读写锁原理

ReentrantReadWriteLock

当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能。 类似于数据库中的 select ...from ... lock in share mode

提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法写锁保护数据的 write() 方法

  1. package reentrantLock;
  2. import lombok.SneakyThrows;
  3. import lombok.extern.slf4j.Slf4j;
  4. import java.util.concurrent.locks.ReentrantReadWriteLock;
  5. import static java.lang.Thread.sleep;
  6. @Slf4j
  7. class DataContainer {
  8. private Object data;
  9. private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
  10. private ReentrantReadWriteLock.ReadLock r = rw.readLock();
  11. private ReentrantReadWriteLock.WriteLock w = rw.writeLock();
  12. @SneakyThrows
  13. public Object read() {
  14. log.debug("获取读锁...");
  15. r.lock();
  16. try {
  17. log.debug("读取");
  18. sleep(1);
  19. return data;
  20. } finally {
  21. log.debug("释放读锁...");
  22. r.unlock();
  23. }
  24. }
  25. @SneakyThrows
  26. public void write() {
  27. log.debug("获取写锁...");
  28. w.lock();
  29. try {
  30. log.debug("写入");
  31. sleep(1);
  32. } finally {
  33. log.debug("释放写锁...");
  34. w.unlock();
  35. }
  36. }
  37. }

测试 读锁-读锁 可以并发

  1. package reentrantLock;
  2. /** * @author 大忽悠 * @create 2022/1/7 13:26 */
  3. public class Main
  4. {
  5. public static void main(String[] args)
  6. {
  7. DataContainer dataContainer = new DataContainer();
  8. new Thread(() -> {
  9. dataContainer.read();
  10. }, "t1").start();
  11. new Thread(() -> {
  12. dataContainer.read();
  13. }, "t2").start();
  14. }
  15. }

输出结果,从这里可以看到 Thread-0 锁定期间,Thread-1 的读操作不受影响

  1. 14:05:14.341 c.DataContainer [t2] - 获取读锁...
  2. 14:05:14.341 c.DataContainer [t1] - 获取读锁...
  3. 14:05:14.345 c.DataContainer [t1] - 读取
  4. 14:05:14.345 c.DataContainer [t2] - 读取
  5. 14:05:15.365 c.DataContainer [t2] - 释放读锁...
  6. 14:05:15.386 c.DataContainer [t1] - 释放读锁...

测试 读锁-写锁 相互阻塞

  1. 在这里插入代码片

输出结果

  1. 14:04:21.838 c.DataContainer [t1] - 获取读锁...
  2. 14:04:21.838 c.DataContainer [t2] - 获取写锁...
  3. 14:04:21.841 c.DataContainer [t2] - 写入
  4. 14:04:22.843 c.DataContainer [t2] - 释放写锁...
  5. 14:04:22.843 c.DataContainer [t1] - 读取
  6. 14:04:23.843 c.DataContainer [t1] - 释放读锁...

写锁-写锁 也是相互阻塞的,这里就不测试了

注意事项

  • 读锁不支持条件变量
  • 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
  1. r.lock();
  2. try {
  3. // ...
  4. w.lock();
  5. try {
  6. // ...
  7. } finally {
  8. w.unlock();
  9. }
  10. } finally {
  11. r.unlock();
  12. }
  • 重入时降级支持:即持有写锁的情况下去获取读锁
  1. package reentrantLock;
  2. import java.util.concurrent.locks.ReentrantReadWriteLock;
  3. class CachedData<ReentrantReadWriteLock> {
  4. Object data;
  5. // 是否有效,如果失效,需要重新计算 data
  6. volatile boolean cacheValid;
  7. final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  8. void processCachedData() {
  9. rwl.readLock().lock();
  10. if (!cacheValid) {
  11. // 获取写锁前必须释放读锁
  12. rwl.readLock().unlock();
  13. rwl.writeLock().lock();
  14. try {
  15. // 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新
  16. if (!cacheValid) {
  17. data = ...
  18. cacheValid = true;
  19. }
  20. // 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存
  21. rwl.readLock().lock();
  22. } finally {
  23. rwl.writeLock().unlock();
  24. }
  25. }
  26. // 自己用完数据, 释放读锁
  27. try {
  28. use(data);
  29. } finally {
  30. rwl.readLock().unlock();
  31. }
  32. }
  33. }

缓存

1. 缓存更新策略

更新时,是先清缓存还是先更新数据库

先清缓存

先更新数据库

补充一种情况,假设查询线程 A 查询数据时恰好缓存数据由于时间到期失效,或是第一次查询

这种情况的出现几率非常小,见 facebook 论文

2. 读写锁实现一致性缓存

使用读写锁实现一个简单的按需加载缓存

  1. package com;
  2. import java.util.Arrays;
  3. import java.util.HashMap;
  4. import java.util.Objects;
  5. import java.util.concurrent.locks.ReentrantReadWriteLock;
  6. class GenericCachedDao<T> {
  7. /** * HashMap 作为缓存非线程安全, 需要保护 */
  8. HashMap<SqlPair, T> map = new HashMap<>();
  9. /** * 读写锁 */
  10. ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  11. /** * 数据库查询类 */
  12. GenericDao genericDao = new GenericDao();
  13. /** * <p> * 更新方法 * </p> * @param sql * @param params * @return */
  14. public int update(String sql, Object... params) {
  15. SqlPair key = new SqlPair(sql, params);
  16. // 加写锁, 防止其它线程对缓存读取和更改
  17. lock.writeLock().lock();
  18. try {
  19. //先更新数据库
  20. int rows = genericDao.update(sql, params);
  21. //再清空缓存
  22. map.clear();
  23. return rows;
  24. } finally {
  25. //释放写锁
  26. lock.writeLock().unlock();
  27. }
  28. }
  29. /** * @param beanClass * @param sql * @param params * @return */
  30. public T queryOne(Class<T> beanClass, String sql, Object... params) {
  31. SqlPair key = new SqlPair(sql, params);
  32. // 加读锁, 防止其它线程对缓存更改
  33. lock.readLock().lock();
  34. try {
  35. T value = map.get(key);
  36. if (value != null) {
  37. return value;
  38. }
  39. } finally {
  40. lock.readLock().unlock();
  41. }
  42. // 加写锁, 防止其它线程对缓存读取和更改
  43. lock.writeLock().lock();
  44. try {
  45. // get 方法上面部分是可能多个线程进来的, 可能已经向缓存填充了数据
  46. // 为防止重复查询数据库, 再次验证
  47. T value = map.get(key);
  48. if (value == null) {
  49. // 如果没有, 查询数据库
  50. value = genericDao.queryOne(beanClass, sql, params);
  51. map.put(key, value);
  52. }
  53. return value;
  54. } finally {
  55. lock.writeLock().unlock();
  56. }
  57. }
  58. // 作为 key 保证其是不可变的
  59. class SqlPair
  60. {
  61. /** * sql */
  62. private String sql;
  63. /** * 参数 */
  64. private Object[] params;
  65. public SqlPair(String sql, Object[] params) {
  66. this.sql = sql;
  67. this.params = params;
  68. }
  69. /** * @param o * @return 这里需要重写equals和hashcode方法,因为默认是根据对象地址进行判断的 */
  70. @Override
  71. public boolean equals(Object o) {
  72. if (this == o) {
  73. return true;
  74. }
  75. if (o == null || getClass() != o.getClass()) {
  76. return false;
  77. }
  78. SqlPair sqlPair = (SqlPair) o;
  79. return sql.equals(sqlPair.sql) && Arrays.equals(params, sqlPair.params);
  80. }
  81. /** * @return 这里的hashcode计算值是以内部属性计算得出的 */
  82. @Override
  83. public int hashCode() {
  84. int result = Objects.hash(sql);
  85. result = 31 * result + Arrays.hashCode(params);
  86. return result;
  87. }
  88. }
  89. }

注意

以上实现体现的是读写锁的应用,保证缓存和数据库的一致性,但有下面的问题没有考虑

  • 适合读多写少,如果写操作比较频繁,以上实现性能低
  • 没有考虑缓存容量
  • 没有考虑缓存过期
  • 只适合单机
  • 并发性还是低,目前只会用一把锁,例如读取表一的同时,需要对表二进行写操作,本是两个互不相关联的操作,却因为用了同一把锁,而会产生阻塞现象,可以尝试将锁进行细粒度划分,例如一个表一把锁
  • 更新方法太过简单粗暴,清空了所有 key(考虑按类型分区或重新设计 key),比如,我对表1进行的更新,却把表一和表二的缓存的清空了,这显然不合理

乐观锁实现:用 CAS 去更新

读写锁原理

1. 图解流程

加锁

读写锁用的是同一个 Sycn 同步器,因此等待队列、state 等也是同一个

t1 w.lock,t2 r.lock

1) t1 成功上锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,而读锁使用的是 state 的高 16 位

tryAcquire(int acquires)源码

  1. protected final boolean tryAcquire(int acquires) {
  2. Thread current = Thread.currentThread();
  3. //获取state的值
  4. int c = getState();
  5. //获取写锁部分
  6. int w = exclusiveCount(c);
  7. //c!=0表示有人加锁了,但是这里高十六位或者第十六位不等于0都会导致c不等于0,无法判断是加了读锁还是写锁
  8. if (c != 0) {
  9. // (Note: if c != 0 and w == 0 then shared count != 0)
  10. //如果写锁部分等于0,表示加的是读锁,读锁和写锁是互斥的,因此这里返回false
  11. //判断写锁是不是自己加的,如果是被人加的,返回false
  12. if (w == 0 || current != getExclusiveOwnerThread())
  13. return false;
  14. //写锁重入次数超过最大值
  15. if (w + exclusiveCount(acquires) > MAX_COUNT)
  16. throw new Error("Maximum lock count exceeded");
  17. // Reentrant acquire
  18. //写锁可重入一次
  19. setState(c + acquires);
  20. return true;
  21. }
  22. //c==0还没有人加锁
  23. //writerShouldBlock--->写锁是否应该阻塞: 这里需要区分公平锁和非公平锁
  24. //公平锁:如果当前同步队列有老二,那么返回true,否则如果同步队列为空或者当前线程就是老二,返回false
  25. //非公平锁:总是返回false
  26. if (writerShouldBlock() ||
  27. //compareAndSetState--->设置c=1,设置写锁部分为1
  28. !compareAndSetState(c, c + acquires))
  29. return false;
  30. //设置当前线程为锁的拥有者
  31. setExclusiveOwnerThread(current);
  32. return true;
  33. }

2)t2 执行 r.lock,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared 返回 -1 表示失败

tryAcquireShared 返回值表示

  • -1 表示失败
  • 0 表示成功,但后继节点不会继续唤醒
  • 正数表示成功,而且数值是还有几个后继节点需要唤醒,读写锁返回 1

tryAcquireShared(int unused)源码

  1. protected final int tryAcquireShared(int unused) {
  2. Thread current = Thread.currentThread();
  3. int c = getState();
  4. //查看是否加了写锁,如果!=0表示加了写锁,加了写锁返回true
  5. if (exclusiveCount(c) != 0 &&
  6. //如果加了写锁,并且是当前线程自己加的,那么返回false---这里是因为同一个线程可以先加写锁后加读锁
  7. //完成锁的降级操作
  8. //如果写锁是另一个线程加的,那么返回false
  9. getExclusiveOwnerThread() != current)
  10. //因为这里写锁是t1加的,而t2想要加读锁,因此这里返回-1
  11. return -1;
  12. .....
  13. return 1;
  14. }
  15. return fullTryAcquireShared(current);
  16. }

3)这时会进入 sync.doAcquireShared(1) 流程,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时 t2 仍处于活跃状态

4)t2 会看看自己的节点是不是老二,如果是,还会再次调用 tryAcquireShared(1) 来尝试获取锁

5)如果没有成功,在 doAcquireSharedfor (;;) 循环一次,把前驱节点的 waitStatus 改为 -1,再 for (;😉 循环一次尝试 tryAcquireShared(1) 如果还不成功,那么在 parkAndCheckInterrupt() 处 park

doAcquireShared(int arg)源码

  1. private void doAcquireShared(int arg) {
  2. //加入节点进队列,如果是一开始没有任何节点在同步队列中,就加入两个节点,一个头结点,一个用来包装线程的节点
  3. final Node node = addWaiter(Node.SHARED);
  4. boolean failed = true;
  5. try {
  6. boolean interrupted = false;
  7. for (;;) {
  8. //t2线程有没有前驱节点
  9. final Node p = node.predecessor();
  10. //如果前驱节点是头节点,说明当前线程还有资格去尝试获取一次锁
  11. if (p == head) {
  12. //返回-1表示失败,返回1表示成功
  13. int r = tryAcquireShared(arg);
  14. //只有获取锁成功后,才能进入下面的逻辑
  15. if (r >= 0) {
  16. setHeadAndPropagate(node, r);
  17. p.next = null; // help GC
  18. if (interrupted)
  19. selfInterrupt();
  20. failed = false;
  21. return;
  22. }
  23. }
  24. //是否应该在获取失败后阻塞住,将前驱节点的值设置为-1,表示有资格唤醒后继节点
  25. //然后第一次调用返回false,再次进入循环,尝试获取锁
  26. if (shouldParkAfterFailedAcquire(p, node) &&
  27. //第二次来到这里,是阻塞住当前线程,返回的值表示当前线程是否被打断
  28. parkAndCheckInterrupt())
  29. //如果被打断了,设置打断标记为true
  30. interrupted = true;
  31. }
  32. } finally {
  33. if (failed)
  34. cancelAcquire(node);
  35. }
  36. }

t3 r.lock,t4 w.lock

这种状态下,假设又有 t3 加读锁和 t4 加写锁,这期间 t1仍然持有锁,就变成了下面的样子

写锁的解锁流程

t1 w.unlock

这时会走到写锁的 sync.release(1) 流程,调用 sync.tryRelease(1) 成功,变成下面的样子

接下来执行唤醒流程 sync.unparkSuccessor,即让老二恢复运行,这时 t2 在 doAcquireShared 内parkAndCheckInterrupt() 处恢复运行

这回再来一次 for (;😉 执行 tryAcquireShared 成功则让读锁计数加一

这时 t2 已经恢复运行,接下来 t2 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点

事情还没完,在 setHeadAndPropagate 方法内还会检查下一个节点是否是 shared,如果是则调用doReleaseShared() 将 head 的状态从 -1 改为 0 并唤醒老二,这时 t3 在 doAcquireShared 内parkAndCheckInterrupt() 处恢复运行

这回再来一次 for (;😉 执行 tryAcquireShared 成功则让读锁计数加

下一个节点不是 shared 了,因此不会继续唤醒 t4 所在节点
release(int arg)

  1. public final boolean release(int arg) {
  2. //尝试释放锁
  3. if (tryRelease(arg)) {
  4. Node h = head;
  5. //头结点是否不为null,并且是否有职责唤醒后继节点
  6. if (h != null && h.waitStatus != 0)
  7. //唤醒后继节点
  8. unparkSuccessor(h);
  9. return true;
  10. }
  11. return false;
  12. }

tryRelease(int releases)

  1. //释放写锁
  2. protected final boolean tryRelease(int releases) {
  3. //当前线程是否持有独占锁
  4. if (!isHeldExclusively())
  5. throw new IllegalMonitorStateException();
  6. //state进行减的操作,这里是减一
  7. //这里有锁重入的情况
  8. int nextc = getState() - releases;
  9. //判断写锁是否被释放成功
  10. boolean free = exclusiveCount(nextc) == 0;
  11. if (free)
  12. //释放成功了设置当前锁的owner为null
  13. setExclusiveOwnerThread(null);
  14. setState(nextc);
  15. return free;
  16. }

被唤醒的线程在doAcquireShared(int arg)方法内被暂停处,恢复运行

  1. private void doAcquireShared(int arg) {
  2. final Node node = addWaiter(Node.SHARED);
  3. boolean failed = true;
  4. try {
  5. boolean interrupted = false;
  6. for (;;) {
  7. //此时线程2的前驱节点是头结点,因此由资格被优先唤醒
  8. final Node p = node.predecessor();
  9. if (p == head) {
  10. //尝试去获取锁---这里加锁成功返回1
  11. int r = tryAcquireShared(arg);
  12. if (r >= 0) {
  13. //断开旧的头结点,将恢复运行的t2节点变成头结点---这个方法是重点,可以看错读锁并发的实现
  14. setHeadAndPropagate(node, r);
  15. //断开旧的头结点
  16. p.next = null; // help GC
  17. if (interrupted)
  18. selfInterrupt();
  19. failed = false;
  20. return;
  21. }
  22. }
  23. //线程2在此处恢复运行
  24. if (shouldParkAfterFailedAcquire(p, node) &&
  25. parkAndCheckInterrupt())
  26. interrupted = true;
  27. }
  28. } finally {
  29. if (failed)
  30. cancelAcquire(node);
  31. }
  32. }

tryAcquireShared(arg);尝试获取读锁成功流程分析

  1. protected final int tryAcquireShared(int unused) {
  2. Thread current = Thread.currentThread();
  3. //查看是否加了写锁,如果!=0表示加了写锁,加了写锁返回true
  4. if (exclusiveCount(c) != 0 &&
  5. //如果加了写锁,并且是当前线程自己加的,那么返回false---这里是因为同一个线程可以先加写锁后加读锁
  6. //完成锁的降级操作
  7. //如果写锁是另一个线程加的,那么返回false
  8. getExclusiveOwnerThread() != current)
  9. return -1;
  10. //成功流程--------------------------------
  11. //获取读锁部分
  12. int r = sharedCount(c);
  13. //读锁是否应该被阻塞主
  14. if (!readerShouldBlock() &&
  15. //读锁是否超过最大计数
  16. r < MAX_COUNT &&
  17. //加上65536,即高位加一,表示加了一个读锁
  18. compareAndSetState(c, c + SHARED_UNIT)) {
  19. //下面是关于读锁的计数,这里先不进行分析
  20. if (r == 0) {
  21. firstReader = current;
  22. firstReaderHoldCount = 1;
  23. } else if (firstReader == current) {
  24. firstReaderHoldCount++;
  25. } else {
  26. HoldCounter rh = cachedHoldCounter;
  27. if (rh == null || rh.tid != getThreadId(current))
  28. cachedHoldCounter = rh = readHolds.get();
  29. else if (rh.count == 0)
  30. readHolds.set(rh);
  31. rh.count++;
  32. }
  33. //加锁成功,返回1
  34. return 1;
  35. }
  36. return fullTryAcquireShared(current);
  37. }

setHeadAndPropagate(Node node, int propagate)

  1. private void setHeadAndPropagate(Node node, int propagate) {
  2. Node h = head; // Record old head for check below
  3. //设置node为头结点
  4. setHead(node);
  5. if (propagate > 0 || h == null || h.waitStatus < 0 ||
  6. (h = head) == null || h.waitStatus < 0) {
  7. //获取当前头结点的下一个节点
  8. Node s = node.next;
  9. //判断后继节点,如果还是共享锁
  10. if (s == null || s.isShared())
  11. //再执行释放锁流程
  12. doReleaseShared();
  13. }
  14. }

doReleaseShared()

  1. private void doReleaseShared() {
  2. for (;;) {
  3. Node h = head;
  4. if (h != null && h != tail) {
  5. int ws = h.waitStatus;
  6. if (ws == Node.SIGNAL) {
  7. //头节点的状态由-1改为0,防止在改动的过程中,其他线程看到头结点的值为-1也进行唤醒操作
  8. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
  9. continue; // loop to recheck cases,失败了重新尝试
  10. //唤醒后继节点
  11. unparkSuccessor(h);
  12. }
  13. else if (ws == 0 &&
  14. !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
  15. continue; // loop on failed CAS
  16. }
  17. if (h == head) // loop if head changed
  18. break;
  19. }
  20. }

从setHeadAndPropagate和doReleaseShared()方法可以看出,一旦一个共享节点被唤醒,他就会唤醒他后面连续的所有共享节点,这就是读读并发体现的原理

共享锁的解锁流程

t2 r.unlock,t3 r.unlock

t2 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,但由于计数还不为零

t3 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,这回计数为零了,进入doReleaseShared() 将头节点从 -1 改为 0 并唤醒老二,即

之后 t4 在 acquireQueued 中 parkAndCheckInterrupt 处恢复运行,再次 for (;😉 这次自己是老二,并且没有其他竞争tryAcquire(1) 成功,修改头结点,流程结束

源码分析

写锁上锁流程
  1. static final class NonfairSync extends Sync {
  2. // ... 省略无关代码
  3. // 外部类 WriteLock 方法, 方便阅读, 放在此处
  4. public void lock() {
  5. sync.acquire(1);
  6. }
  7. // AQS 继承过来的方法, 方便阅读, 放在此处
  8. public final void acquire(int arg) {
  9. if (
  10. // 尝试获得写锁失败
  11. !tryAcquire(arg) &&
  12. // 将当前线程关联到一个 Node 对象上, 模式为独占模式
  13. // 进入 AQS 队列阻塞
  14. acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
  15. ) {
  16. selfInterrupt();
  17. }
  18. }
  19. // Sync 继承过来的方法, 方便阅读, 放在此处
  20. protected final boolean tryAcquire(int acquires) {
  21. // 获得低 16 位, 代表写锁的 state 计数
  22. Thread current = Thread.currentThread();
  23. int c = getState();
  24. int w = exclusiveCount(c);
  25. if (c != 0) {
  26. if (
  27. // c != 0 and w == 0 表示有读锁, 或者
  28. w == 0 ||
  29. // 如果 exclusiveOwnerThread 不是自己
  30. current != getExclusiveOwnerThread()
  31. ) {
  32. // 获得锁失败
  33. return false;
  34. }
  35. // 写锁计数超过低 16 位, 报异常
  36. if (w + exclusiveCount(acquires) > MAX_COUNT)
  37. throw new Error("Maximum lock count exceeded");
  38. // 写锁重入, 获得锁成功
  39. setState(c + acquires);
  40. return true;
  41. }
  42. if (
  43. // 判断写锁是否该阻塞, 或者
  44. writerShouldBlock() ||
  45. // 尝试更改计数失败
  46. !compareAndSetState(c, c + acquires)
  47. ) {
  48. // 获得锁失败
  49. return false;
  50. }
  51. // 获得锁成功
  52. setExclusiveOwnerThread(current);
  53. return true;
  54. }
  55. // 非公平锁 writerShouldBlock 总是返回 false, 无需阻塞
  56. final boolean writerShouldBlock() {
  57. return false;
  58. }
  59. }
写锁释放流程
  1. static final class NonfairSync extends Sync {
  2. // ... 省略无关代码
  3. // WriteLock 方法, 方便阅读, 放在此处
  4. public void unlock() {
  5. sync.release(1);
  6. }
  7. // AQS 继承过来的方法, 方便阅读, 放在此处
  8. public final boolean release(int arg) {
  9. // 尝试释放写锁成功
  10. if (tryRelease(arg)) {
  11. // unpark AQS 中等待的线程
  12. Node h = head;
  13. if (h != null && h.waitStatus != 0)
  14. unparkSuccessor(h);
  15. return true;
  16. }
  17. return false;
  18. }
  19. // Sync 继承过来的方法, 方便阅读, 放在此处
  20. protected final boolean tryRelease(int releases) {
  21. if (!isHeldExclusively())
  22. throw new IllegalMonitorStateException();
  23. int nextc = getState() - releases;
  24. // 因为可重入的原因, 写锁计数为 0, 才算释放成功
  25. boolean free = exclusiveCount(nextc) == 0;
  26. if (free) {
  27. setExclusiveOwnerThread(null);
  28. }
  29. setState(nextc);
  30. return free;
  31. }
  32. }
读锁上锁流程
  1. package com;
  2. static final class NonfairSync extends Sync {
  3. // ReadLock 方法, 方便阅读, 放在此处
  4. public void lock() {
  5. sync.acquireShared(1);
  6. }
  7. // AQS 继承过来的方法, 方便阅读, 放在此处
  8. public final void acquireShared(int arg) {
  9. // tryAcquireShared 返回负数, 表示获取读锁失败
  10. if (tryAcquireShared(arg) < 0) {
  11. doAcquireShared(arg);
  12. }
  13. }
  14. // Sync 继承过来的方法, 方便阅读, 放在此处
  15. protected final int tryAcquireShared(int unused) {
  16. Thread current = Thread.currentThread();
  17. int c = getState();
  18. // 如果是其它线程持有写锁, 获取读锁失败
  19. if (
  20. exclusiveCount(c) != 0 &&
  21. getExclusiveOwnerThread() != current
  22. ) {
  23. return -1;
  24. }
  25. int r = sharedCount(c);
  26. if (
  27. // 读锁不该阻塞(如果老二是写锁,读锁该阻塞), 并且
  28. !readerShouldBlock() &&
  29. // 小于读锁计数, 并且
  30. r < MAX_COUNT &&
  31. // 尝试增加计数成功
  32. compareAndSetState(c, c + SHARED_UNIT)
  33. ) {
  34. // ... 省略不重要的代码
  35. return 1;
  36. }
  37. return fullTryAcquireShared(current);
  38. }
  39. // 非公平锁 readerShouldBlock 看 AQS 队列中第一个节点是否是写锁
  40. // true 则该阻塞, false 则不阻塞
  41. final boolean readerShouldBlock() {
  42. return apparentlyFirstQueuedIsExclusive();
  43. }
  44. // AQS 继承过来的方法, 方便阅读, 放在此处
  45. // 与 tryAcquireShared 功能类似, 但会不断尝试 for (;;) 获取读锁, 执行过程中无阻塞
  46. final int fullTryAcquireShared(Thread current) {
  47. HoldCounter rh = null;
  48. for (; ; ) {
  49. int c = getState();
  50. if (exclusiveCount(c) != 0) {
  51. if (getExclusiveOwnerThread() != current)
  52. return -1;
  53. } else if (readerShouldBlock()) {
  54. // ... 省略不重要的代码
  55. }
  56. if (sharedCount(c) == MAX_COUNT)
  57. throw new Error("Maximum lock count exceeded");
  58. if (compareAndSetState(c, c + SHARED_UNIT)) {
  59. // ... 省略不重要的代码
  60. return 1;
  61. }
  62. }
  63. }
  64. // AQS 继承过来的方法, 方便阅读, 放在此处
  65. private void doAcquireShared(int arg) {
  66. // 将当前线程关联到一个 Node 对象上, 模式为共享模式
  67. final Node node = addWaiter(Node.SHARED);
  68. boolean failed = true;
  69. try {
  70. boolean interrupted = false;
  71. for (; ; ) {
  72. final Node p = node.predecessor();
  73. if (p == head) { // 再一次尝试获取读锁
  74. int r = tryAcquireShared(arg);
  75. // 成功
  76. if (r >= 0) {
  77. // ㈠
  78. // r 表示可用资源数, 在这里总是 1 允许传播
  79. //(唤醒 AQS 中下一个 Share 节点)
  80. setHeadAndPropagate(node, r);
  81. p.next = null; // help GC
  82. if (interrupted)
  83. selfInterrupt();
  84. failed = false;
  85. return;
  86. }
  87. }
  88. if (
  89. // 是否在获取读锁失败时阻塞(前一个阶段 waitStatus == Node.SIGNAL)
  90. shouldParkAfterFailedAcquire(p, node) &&
  91. // park 当前线程
  92. parkAndCheckInterrupt()
  93. ) {
  94. interrupted = true;
  95. }
  96. }
  97. } finally {
  98. if (failed)
  99. cancelAcquire(node);
  100. }
  101. }
  102. // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
  103. private void setHeadAndPropagate(Node node, int propagate) {
  104. Node h = head; // Record old head for check below
  105. // 设置自己为 head
  106. setHead(node);
  107. // propagate 表示有共享资源(例如共享读锁或信号量)
  108. // 原 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
  109. // 现在 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
  110. if (propagate > 0 || h == null || h.waitStatus < 0 ||
  111. (h = head) == null || h.waitStatus < 0) {
  112. Node s = node.next;
  113. // 如果是最后一个节点或者是等待共享读锁的节点
  114. if (s == null || s.isShared()) {
  115. // 进入 ㈡
  116. doReleaseShared();
  117. }
  118. }
  119. }
  120. // ㈡ AQS 继承过来的方法, 方便阅读, 放在此处
  121. private void doReleaseShared() {
  122. // 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
  123. // 如果 head.waitStatus == 0 ==> Node.PROPAGATE, 为了解决 bug, 见后面分析
  124. for (; ; ) {
  125. Node h = head;
  126. // 队列还有节点
  127. if (h != null && h != tail) {
  128. int ws = h.waitStatus;
  129. if (ws == Node.SIGNAL) {
  130. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
  131. continue; // loop to recheck cases
  132. // 下一个节点 unpark 如果成功获取读锁
  133. // 并且下下个节点还是 shared, 继续 doReleaseShared
  134. unparkSuccessor(h);
  135. } else if (ws == 0 &&
  136. !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
  137. continue; // loop on failed CAS
  138. }
  139. if (h == head) // loop if head changed
  140. break;
  141. }
  142. }
  143. }
  144. static final class NonfairSync extends Sync {
  145. // ReadLock 方法, 方便阅读, 放在此处
  146. public void unlock() {
  147. sync.releaseShared(1);
  148. }
  149. // AQS 继承过来的方法, 方便阅读, 放在此处
  150. public final boolean releaseShared(int arg) {
  151. if (tryReleaseShared(arg)) {
  152. doReleaseShared();
  153. return true;
  154. }
  155. return false;
  156. }
  157. // Sync 继承过来的方法, 方便阅读, 放在此处
  158. protected final boolean tryReleaseShared(int unused) {
  159. // ... 省略不重要的代码
  160. for (; ; ) {
  161. int c = getState();
  162. int nextc = c - SHARED_UNIT;
  163. if (compareAndSetState(c, nextc)) {
  164. // 读锁的计数不会影响其它获取读锁线程, 但会影响其它获取写锁线程
  165. // 计数为 0 才是真正释放
  166. return nextc == 0;
  167. }
  168. }
  169. }
  170. // AQS 继承过来的方法, 方便阅读, 放在此处
  171. private void doReleaseShared() {
  172. // 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
  173. // 如果 head.waitStatus == 0 ==> Node.PROPAGATE
  174. for (; ; ) {
  175. Node h = head;
  176. if (h != null && h != tail) {
  177. int ws = h.waitStatus;
  178. // 如果有其它线程也在释放读锁,那么需要将 waitStatus 先改为 0
  179. // 防止 unparkSuccessor 被多次执行
  180. if (ws == Node.SIGNAL) {
  181. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
  182. continue; // loop to recheck cases
  183. unparkSuccessor(h);
  184. }
  185. // 如果已经是 0 了,改为 -3,用来解决传播性,见后文信号量 bug 分析
  186. else if (ws == 0 &&
  187. !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
  188. continue; // loop on failed CAS
  189. }
  190. if (h == head) // loop if head changed
  191. break;
  192. }
  193. }
  194. }

相关文章