我们调用Semaphore方法时,其实是在间接调用其内部类或AQS方法执行的。Semaphore类结构与ReetrantLock类相似,内部类Sync继承自AQS,然后其子类FairSync和NoFairSync分别实现公平锁和非公平锁的获取锁方法tryAcquireShared(int arg),而释放锁的tryReleaseShared(int arg)方法则有Sync类实现,因为非公平或公平锁的释放过程都是相同的。

AQS通过state值来控制对共享资源访问的线程数,有线程请求同步状态成功state值减1,若超过共享资源数量获取同步状态失败,则将线程封装共享模式的Node结点加入到同步队列等待。有线程执行完任务释放同步状态后,state值会增加1,同步队列中的线程才有机会获得执行权。公平锁与非公平锁不同在于公平锁申请获取同步状态前都会先判断同步队列中释放存在Node,若有则将当前线程封装成Node结点入队,从而保证按FIFO的方式获取同步状态,而非公平锁则可以直接通过竞争获取线程执行权。

  1. //Semaphore的acquire()
  2. public void acquire() throws InterruptedException {
  3. sync.acquireSharedInterruptibly(1);
  4. }
  5.  
  6. /**
  7. * 注意Sync类继承自AQS
  8. * AQS的acquireSharedInterruptibly()方法
  9. */
  10. public final void acquireSharedInterruptibly(int arg)
  11. throws InterruptedException {
  12. //判断是否中断请求
  13. if (Thread.interrupted())
  14. throw new InterruptedException();
  15. //如果tryAcquireShared(arg)不小于0,则线程获取同步状态成功
  16. if (tryAcquireShared(arg) < 0)
  17. //未获取成功加入同步队列等待
  18. doAcquireSharedInterruptibly(arg);
  19. }
  20. //Semaphore中非公平锁NonfairSync的tryAcquireShared()
  21. protected int tryAcquireShared(int acquires) {
  22. //调用了父类Sync中的实现方法
  23. return nonfairTryAcquireShared(acquires);
  24. }
  25.  
  26. final int nonfairTryAcquireShared(int acquires) {
  27. //使用死循环
  28. for (;;) {
  29. int available = getState();
  30. int remaining = available - acquires;
  31. //判断信号量是否已小于0或者CAS执行是否成功
  32. if (remaining < 0 ||
  33. compareAndSetState(available, remaining))
  34. return remaining;
  35. }
  36. }
  37. private void doAcquireSharedInterruptibly(int arg)
  38. throws InterruptedException {
  39. //创建共享模式的结点Node.SHARED,并加入同步队列
  40. final Node node = addWaiter(Node.SHARED);
  41. boolean failed = true;
  42. try {
  43. //进入自旋操作
  44. for (;;) {
  45. final Node p = node.predecessor();
  46. //判断前驱结点是否为head
  47. if (p == head) {
  48. //尝试获取同步状态
  49. int r = tryAcquireShared(arg);
  50. //如果r>0 说明获取同步状态成功
  51. if (r >= 0) {
  52. //将当前线程结点设置为头结点并传播
  53. setHeadAndPropagate(node, r);
  54. p.next = null; // help GC
  55. failed = false;
  56. return;
  57. }
  58. }
  59. //调整同步队列中node结点的状态并判断是否应该被挂起
  60. //并判断是否需要被中断,如果中断直接抛出异常,当前结点请求也就结束
  61. if (shouldParkAfterFailedAcquire(p, node) &&
  62. parkAndCheckInterrupt())
  63. throw new InterruptedException();
  64. }
  65. } finally {
  66. if (failed)
  67. //结束该结点线程的请求
  68. cancelAcquire(node);
  69. }
  70. }
  71.  
  72. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  73. //获取当前结点的等待状态
  74. int ws = pred.waitStatus;
  75. //如果为等待唤醒(SIGNAL)状态则返回true
  76. if (ws == Node.SIGNAL)
  77. return true;
  78. //如果ws>0 则说明是结束状态,
  79. //遍历前驱结点直到找到没有结束状态的结点
  80. if (ws > 0) {
  81. do {
  82. node.prev = pred = pred.prev;
  83. } while (pred.waitStatus > 0);
  84. pred.next = node;
  85. } else {
  86. //如果ws小于0又不是SIGNAL状态,
  87. //则将其设置为SIGNAL状态,代表该结点的线程正在等待唤醒。
  88. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  89. }
  90. return false;
  91. }
  92.  
  93. private final boolean parkAndCheckInterrupt() {
  94. //将当前线程挂起
  95. LockSupport.park(this);
  96. //获取线程中断状态,interrupted()是判断当前中断状态,
  97. //并非中断线程,因此可能true也可能false,并返回
  98. return Thread.interrupted();
  99. }
  100.  
  101. //不可中的acquireShared()
  102. public final void acquireShared(int arg) {
  103. if (tryAcquireShared(arg) < 0)
  104. doAcquireShared(arg);
  105. }
  106.  
  107. private void doAcquireShared(int arg) {
  108. final Node node = addWaiter(Node.SHARED);
  109. boolean failed = true;
  110. try {
  111. boolean interrupted = false;
  112. for (;;) {
  113. final Node p = node.predecessor();
  114. if (p == head) {
  115. int r = tryAcquireShared(arg);
  116. if (r >= 0) {
  117. setHeadAndPropagate(node, r);
  118. p.next = null; // help GC
  119. if (interrupted)
  120. selfInterrupt();
  121. failed = false;
  122. return;
  123. }
  124. }
  125. if (shouldParkAfterFailedAcquire(p, node) &&
  126. parkAndCheckInterrupt())
  127. //没有抛出异常中的。。。。
  128. interrupted = true;
  129. }
  130. } finally {
  131. if (failed)
  132. cancelAcquire(node);
  133. }
  134. }
  135.  
  136. private void setHeadAndPropagate(Node node, int propagate) {
  137. Node h = head; // Record old head for check below
  138. setHead(node);//设置为头结点
  139. /*
  140. * 尝试去唤醒队列中的下一个节点,如果满足如下条件:
  141. * 调用者明确表示"传递"(propagate > 0),
  142. * 或者h.waitStatus为PROPAGATE(被上一个操作设置)
  143. * 并且
  144. * 下一个节点处于共享模式或者为null。
  145. *
  146. * 这两项检查中的保守主义可能会导致不必要的唤醒,但只有在有
  147. * 有在多个线程争取获得/释放同步状态时才会发生,所以大多
  148. * 数情况下会立马获得需要的信号
  149. */
  150. if (propagate > 0 || h == null || h.waitStatus < 0 ||
  151. (h = head) == null || h.waitStatus < 0) {
  152. Node s = node.next;
  153. if (s == null || s.isShared())
  154. //唤醒后继节点,因为是共享模式,所以允许多个线程同时获取同步状态
  155. doReleaseShared();
  156. }
  157. }
  158.  
  159. //Semaphore的release()
  160. public void release() {
  161. sync.releaseShared(1);
  162. }
  163.  
  164. //调用到AQS中的releaseShared(int arg)
  165. public final boolean releaseShared(int arg) {
  166. //调用子类Semaphore实现的tryReleaseShared方法尝试释放同步状态
  167. if (tryReleaseShared(arg)) {
  168. doReleaseShared();
  169. return true;
  170. }
  171. return false;
  172. }
  173.  
  174. //在Semaphore的内部类Sync中实现的
  175. protected final boolean tryReleaseShared(int releases) {
  176. for (;;) {
  177. //获取当前state
  178. int current = getState();
  179. //释放状态state增加releases
  180. int next = current + releases;
  181. if (next < current) // overflow
  182. throw new Error("Maximum permit count exceeded");
  183. //通过CAS更新state的值
  184. if (compareAndSetState(current, next))
  185. return true;
  186. }
  187. }
  188.  
  189. private void doReleaseShared() {
  190. /*
  191. * 保证释放动作(向同步等待队列尾部)传递,即使没有其他正在进行的
  192. * 请求或释放动作。如果头节点的后继节点需要唤醒,那么执行唤醒
  193. * 动作;如果不需要,将头结点的等待状态设置为PROPAGATE保证
  194. * 唤醒传递。另外,为了防止过程中有新节点进入(队列),这里必
  195. * 需做循环,所以,和其他unparkSuccessor方法使用方式不一样
  196. * 的是,如果(头结点)等待状态设置失败,重新检测。
  197. */
  198. for (;;) {
  199. Node h = head;
  200. if (h != null && h != tail) {
  201. // 获取头节点对应的线程的状态
  202. int ws = h.waitStatus;
  203. // 如果头节点对应的线程是SIGNAL状态,则意味着头
  204. //结点的后继结点所对应的线程需要被unpark唤醒。
  205. if (ws == Node.SIGNAL) {
  206. // 修改头结点对应的线程状态设置为0。失败的话,则继续循环。
  207. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
  208. continue;
  209. // 唤醒头结点h的后继结点所对应的线程
  210. unparkSuccessor(h);
  211. }
  212. else if (ws == 0 &&
  213. !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
  214. continue; // loop on failed CAS
  215. }
  216. // 如果头结点发生变化,则继续循环。否则,退出循环。
  217. if (h == head) // loop if head changed
  218. break;
  219. }
  220. }
  221.  
  222. //唤醒传入结点的后继结点对应的线程
  223. private void unparkSuccessor(Node node) {
  224. int ws = node.waitStatus;
  225. if (ws < 0)
  226. compareAndSetWaitStatus(node, ws, 0);
  227. //拿到后继结点
  228. Node s = node.next;
  229. if (s == null || s.waitStatus > 0) {
  230. s = null;
  231. for (Node t = tail; t != null && t != node; t = t.prev)
  232. if (t.waitStatus <= 0)
  233. s = t;
  234. }
  235. if (s != null)
  236. //唤醒该线程
  237. LockSupport.unpark(s.thread);
  238. }

剖析基于并发AQS的共享锁的实现(基于信号量Semaphore)

AQS共享锁应用之Semaphore原理的更多相关文章

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

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

  2. 面经手册 · 第18篇《AQS 共享锁,Semaphore、CountDownLatch,听说数据库连接池可以用到!》

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

  3. Java 中队列同步器 AQS(AbstractQueuedSynchronizer)实现原理

    前言 在 Java 中通过锁来控制多个线程对共享资源的访问,使用 Java 编程语言开发的朋友都知道,可以通过 synchronized 关键字来实现锁的功能,它可以隐式的获取锁,也就是说我们使用该关 ...

  4. 一文搞懂AQS及其组件的核心原理

    @ 目录 前言 AbstractQueuedSynchronizer Lock ReentrantLock 加锁 非公平锁/公平锁 lock tryAcquire addWaiter acquireQ ...

  5. AQS的数据结构及实现原理

    接下来从实现角度来分析同步器是如何完成线程同步的.主要包括:同步队列.独占式同步状态获取与释放.共享式同步状态获取与释放以及超时获取同步状态等. 1.同步队列 同步器依赖内部的一个同步队列来完成同步状 ...

  6. AQS之CountDownLatch、Semaphore、CyclicBarrier

    CountDownLatch A synchronization aid that allows one or more threads to wait until a set of operatio ...

  7. Java并发——结合CountDownLatch源码、Semaphore源码及ReentrantLock源码来看AQS原理

    前言: 如果说J.U.C包下的核心是什么?那我想答案只有一个就是AQS.那么AQS是什么呢?接下来让我们一起揭开AQS的神秘面纱 AQS是什么? AQS是AbstractQueuedSynchroni ...

  8. CountDownLatch、CyclicBarrier和Semaphore 使用示例及原理

    备注:博客园的markDown格式支持的特别不友好.也欢迎查看我的csdn的此篇文章链接:CountDownLatch.CyclicBarrier和Semaphore 使用示例及原理 CountDow ...

  9. 深入浅出AQS之共享锁模式

    在了解了AQS独占锁模式以后,接下来再来看看共享锁的实现原理. 原文地址:http://www.jianshu.com/p/1161d33fc1d0 搞清楚AQS独占锁的实现原理之后,再看共享锁的实现 ...

随机推荐

  1. VMware安装黑群暉5.2

      选择典型就可以了,点击下一步. 选择 稍后安装操作系统,点击下一步. 客户机操作系统选择Linux,版本选择其他Linux2.6.x内核64位, 填写虚拟机名称和虚拟机文件保存位置的.填写好后点击 ...

  2. C# Ftp方式下载文件(无用户认证方式,支持断点续传)

    类代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; using Sys ...

  3. -webkit-transform:translate3d(0,0,0)触发GPU加速,让网页动画更流畅

    前段时间,依照美拍的视频效果写了一个效果类似的网页版的动画. 电脑上安装了三种浏览器:IE.Chrome.Firefox.分别作了測试,结果显示Chrome在这方面的渲染效果最差.常常出现卡顿现象.f ...

  4. HDOJ题目2089 不要62(数位DP)

    不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

  5. soapUI学习笔记--用例字段参数化

    字段参数化的简单操作 1.把Request新增一个TestCase 增加TestCase,下方会出现: 2.案例中,请求参数只有一个.先运行下请求,可以运行成功(保证接口是通的) 3.添加参数.见图中 ...

  6. How to reset your password in Ubuntu

    There are many reasons you might want to reset a password: Someone gave you a computer with Ubuntu i ...

  7. Sass编译css/Grunt压缩文件

    Sass安装(mac) $ sudo gem install sass scss编译成css文件 $ sass ui.scss ui.css CLI安装 $ npm install -g grunt- ...

  8. android handler looper

    http://www.cnblogs.com/plokmju/p/android_Handler.html

  9. Android 四大组件学习之BroadcastReceiver一

    本节课学习四大组件最后一个, 广播接受者. 顾名思义广播接受者就是接受广播呗.比方在现实社会中,曾经每一个人家都有一台收音机,这可就能够去接受广播发出来的消息.大家都知道.程序世界也是參照的显示生活设 ...

  10. Why containers? Why should we care? 新旧容器的对比

    https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/ The Old Way to deploy applications ...