image.png初识Semaphore:

Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,一般可用于流量的控制。

Semaphore用于限制可以访问某些资源(物理或逻辑的)的线程数目,他维护了一个许可证集合,有多少资源需要限制就维护多少许可证集合,假如这里有N个资源,那就对应于N个许可证,同一时刻也只能有N个线程访问。一个线程获取许可证就调用acquire方法,用完了释放资源就调用release方法。

Semaphore管理一系列许可。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可这个对象,Semaphore只是维持了一个可获得许可证的数量。

Semaphore 只有3个操作:

  • 初始化
  • 增加
  • 减少

    API解读:

    image.png
  • 构造方法一:Semaphore(int permits) 创建具有给定的许可数和非公平的公平设置的 Semaphore

    1. public Semaphore(int permits) {
    2. sync = new NonfairSync(permits);
    3. }
  • 构造方法二:Semaphore(int permits, boolean fair) 创建具有给定的许可数和给定的公平设置的 Semaphore

    1. /**
    2. * @param fair :是否公平,默认非公平;
    3. * false:非公平;true:公平
    4. */
    5. public Semaphore(int permits, boolean fair) {
    6. sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    7. }
  • acquire() 获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。

    1. public void acquire() throws InterruptedException {
    2. sync.acquireSharedInterruptibly(1);
    3. }
  • acquire(int permits) 获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。

    1. public void acquire(int permits) throws InterruptedException {
    2. if (permits < 0) throw new IllegalArgumentException();
    3. sync.acquireSharedInterruptibly(permits);
    4. }
  • acquireUninterruptibly() 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。

    1. public void acquireUninterruptibly(int permits) {
    2. if (permits < 0) throw new IllegalArgumentException();
    3. sync.acquireShared(permits);
    4. }
  • tryAcquire()尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。

    1. public boolean tryAcquire() {
    2. return sync.nonfairTryAcquireShared(1) >= 0;
    3. }
  • tryAcquire(long timeout, TimeUnit unit)尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。

    1. public boolean tryAcquire(long timeout, TimeUnit unit)
    2. throws InterruptedException {
    3. return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    4. }
  • release() 释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。

    1. public void release() {
    2. sync.releaseShared(1);
    3. }
  • release(int permits)释放给定数目的许可,将其返回到信号量

    1. public void release(int permits) {
    2. if (permits < 0) throw new IllegalArgumentException();
    3. sync.releaseShared(permits);
    4. }
  • hasQueuedThreads() 等待队列里是否还存在等待线程。

    1. public final boolean hasQueuedThreads() {
    2. return sync.hasQueuedThreads();
    3. }
  • getQueueLength()获取等待队列里阻塞的线程数。

    1. public final int getQueueLength() {
    2. return sync.getQueueLength();
    3. }
  • drainPermits()清空令牌把可用令牌数置为0,返回清空令牌的数量。

    1. public int drainPermits() {
    2. return sync.drainPermits();
    3. }
  • availablePermits()返回可用的令牌数量

    1. public int availablePermits() {
    2. return sync.getPermits();
    3. }

    代码示例:
    场景分析:相信有车一族最头疼的就是停车了吧,每次到一个景点去停车,都会被保安告诉车位已满,需要等待,并且只能有车离开才能让下一辆车进去;有的车库在门前会显示有几个车位,剩余多少车位。那么这个功能通过代码是如何实现的呢?简易版代码如下: ```java public class SemaphoreTest {

    private static int count = 20; //停车场同时容纳的车辆8, 此处默认使用非公平的锁,你们可以私下去使用公平锁去试试,就会发现,先到先得 // 写法示例: Semaphore semaphore = new Semaphore(8, true); private static Semaphore semaphore = new Semaphore(8);

    private static ExecutorService service = Executors.newFixedThreadPool(count);

    public static void main(String[] args) { test(); }

    public static void test() {

    1. //模拟100辆车进入停车场
    2. for (int i = 0; i < 20; i++) {
    3. int num = i + 1;
    4. service.execute(() -> {
    5. try {
    6. System.out.println("车牌 00000" + num + "来到停车场");
    7. if (semaphore.availablePermits() == 0) {
    8. System.out.println("车位不足,请耐心等待");
    9. }
    10. semaphore.acquire();
    11. System.out.println("车牌 00000" + num + "成功进入停车场");
    12. Thread.sleep(new Random().nextInt(10000));
    13. System.out.println("车牌 00000" + num + "驶出停车场");
    14. semaphore.release();//释放令牌,腾出停车场车位
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. });
    19. }
    20. service.shutdown();

    } }

  1. 执行结果:
  2. > 车牌 000001来到停车场
  3. > 车牌 000003来到停车场
  4. > 车牌 000002来到停车场
  5. > 车牌 000003成功进入停车场
  6. > 车牌 000001成功进入停车场
  7. > 车牌 000005来到停车场
  8. > 车牌 000005成功进入停车场
  9. > 车牌 000006来到停车场
  10. > 车牌 000006成功进入停车场
  11. > 车牌 000004来到停车场
  12. > 车牌 000002成功进入停车场
  13. > 车牌 000004成功进入停车场
  14. > 车牌 000007来到停车场
  15. > 车牌 000007成功进入停车场
  16. > 车牌 000008来到停车场
  17. > 车牌 000008成功进入停车场
  18. > ==================================
  19. > 车牌 000009来到停车场
  20. > 车位不足,请耐心等待
  21. > 车牌 0000010来到停车场
  22. > 车位不足,请耐心等待
  23. > 车牌 0000011来到停车场
  24. > 车位不足,请耐心等待
  25. > 车牌 0000012来到停车场
  26. > 车位不足,请耐心等待
  27. > 车牌 0000013来到停车场
  28. > 车位不足,请耐心等待
  29. > 车牌 0000014来到停车场
  30. > 车位不足,请耐心等待
  31. > 车牌 0000015来到停车场
  32. > 车位不足,请耐心等待
  33. > 车牌 0000016来到停车场
  34. > 车位不足,请耐心等待
  35. > 车牌 0000017来到停车场
  36. > 车位不足,请耐心等待
  37. > 车牌 0000018来到停车场
  38. > 车位不足,请耐心等待
  39. > 车牌 0000019来到停车场
  40. > 车位不足,请耐心等待
  41. > 车牌 0000020来到停车场
  42. > 车位不足,请耐心等待
  43. > ===================================
  44. > 车牌 000004驶出停车场
  45. > 车牌 000009成功进入停车场
  46. > 车牌 000002驶出停车场
  47. > 车牌 0000010成功进入停车场
  48. > 车牌 0000010驶出停车场
  49. > 车牌 0000011成功进入停车场
  50. > 车牌 0000011驶出停车场
  51. > 车牌 0000012成功进入停车场
  52. > 车牌 000003驶出停车场
  53. > 车牌 0000013成功进入停车场
  54. > 车牌 000006驶出停车场
  55. > 车牌 0000014成功进入停车场
  56. > 车牌 000008驶出停车场
  57. > 车牌 0000015成功进入停车场
  58. > 车牌 000007驶出停车场
  59. > 车牌 0000016成功进入停车场
  60. > 车牌 0000015驶出停车场
  61. > 车牌 0000017成功进入停车场
  62. > 车牌 000005驶出停车场
  63. > 车牌 0000018成功进入停车场
  64. > 车牌 000009驶出停车场
  65. > 车牌 0000019成功进入停车场
  66. > 车牌 0000019驶出停车场
  67. > 车牌 0000020成功进入停车场
  68. > 车牌 000001驶出停车场
  69. > 车牌 0000012驶出停车场
  70. > 车牌 0000017驶出停车场
  71. > 车牌 0000016驶出停车场
  72. > 车牌 0000018驶出停车场
  73. > 车牌 0000013驶出停车场
  74. > 车牌 0000014驶出停车场
  75. > 车牌 0000020驶出停车场
  76. <a name="sm4ko"></a>
  77. ## 源码解析:(默认使用不公平许可证)
  78. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1692011/1611893547674-6d0e200e-ebe1-4c42-98a1-ce94d11d73be.png#align=left&display=inline&height=659&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1318&originWidth=1490&size=1773166&status=done&style=none&width=745)<br />通过上面案例,我们可以发现,首先通过 **semaphore.availablePermits() **当前资源是否充足,不足则告诉其他车主,停车场满了别进来了。然后调用 **semaphore.acquire() **去获取令牌,源码如下:
  79. ```java
  80. /**
  81. * 获取1个令牌
  82. */
  83. public void acquire() throws InterruptedException {
  84. sync.acquireSharedInterruptibly(1);
  85. }
  86. /**
  87. * 共享模式下获取令牌,获取成功则返回,失败则加入阻塞队列,挂起线程
  88. */
  89. public final void acquireSharedInterruptibly(int arg)
  90. throws InterruptedException {
  91. if (Thread.interrupted())
  92. throw new InterruptedException();
  93. //尝试获取令牌,arg为获取令牌个数,当可用令牌数减当前令牌数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程
  94. if (tryAcquireShared(arg) < 0)
  95. doAcquireSharedInterruptibly(arg);
  96. }
  97. /**
  98. * 1、创建节点,加入阻塞队列,
  99. * 2、重双向链表的head,tail节点关系,清空无效节点
  100. * 3、挂起当前节点线程
  101. */
  102. private void doAcquireSharedInterruptibly(int arg)
  103. throws InterruptedException {
  104. //创建节点加入阻塞队列
  105. final Node node = addWaiter(Node.SHARED);
  106. boolean failed = true;
  107. try {
  108. for (;;) {
  109. //获得当前节点pre节点
  110. final Node p = node.predecessor();
  111. if (p == head) {
  112. //返回锁的state
  113. int r = tryAcquireShared(arg);
  114. if (r >= 0) {
  115. setHeadAndPropagate(node, r);
  116. p.next = null; // help GC
  117. failed = false;
  118. return;
  119. }
  120. }
  121. //重组双向链表,清空无效节点,挂起当前线程
  122. if (shouldParkAfterFailedAcquire(p, node) &&
  123. parkAndCheckInterrupt())
  124. throw new InterruptedException();
  125. }
  126. } finally {
  127. if (failed)
  128. cancelAcquire(node);
  129. }
  130. }

获取到了车位,然后开始停车。。。。。。下班了,开车回家,此时我们是不是要离开车库,即去释放车位;image.png
源码如下:

  1. /**
  2. * 释放令牌
  3. */
  4. public void release() {
  5. sync.releaseShared(1);
  6. }
  7. /**
  8. * 释放共享锁,同时唤醒所有阻塞队列共享节点线程
  9. */
  10. public final boolean releaseShared(int arg) {
  11. //释放共享锁
  12. if (tryReleaseShared(arg)) {
  13. //唤醒所有共享节点线程
  14. doReleaseShared();
  15. return true;
  16. }
  17. return false;
  18. }
  19. /**
  20. * 共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
  21. */
  22. protected final boolean tryReleaseShared(int releases) {
  23. for (;;) {
  24. int current = getState();
  25. int next = current + releases;
  26. if (next < current) // overflow
  27. throw new Error("Maximum permit count exceeded");
  28. if (compareAndSetState(current, next))
  29. return true;
  30. }
  31. }
  32. /**
  33. * 唤醒所有共享节点线程
  34. */
  35. private void doReleaseShared() {
  36. for (;;) {
  37. Node h = head;
  38. if (h != null && h != tail) {
  39. int ws = h.waitStatus;
  40. if (ws == Node.SIGNAL) {//是否需要唤醒后继节点
  41. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改状态为初始0
  42. continue;
  43. unparkSuccessor(h);//唤醒h.nex节点线程
  44. }
  45. else if (ws == 0 &&
  46. !compareAndSetWaitStatus(h, 0, Node.PROPAGATE));
  47. }
  48. if (h == head) // loop if head changed
  49. break;
  50. }
  51. }

参考文章:https://zhuanlan.zhihu.com/p/98593407