暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

AQS源码分析之Semaphore

开发架构二三事 2020-03-20
148

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。比如控制用户的访问量,同一时刻只允许1000个用户同时使用系统,如果超过1000个并发,则需要等待。本篇来分析java.util.concurrent.Semaphore的源码。

内部类

java.util.concurrent.Semaphore.Sync

  1. // 用AQS的state来代表permits值

  2. abstract static class Sync extends AbstractQueuedSynchronizer {

  3. private static final long serialVersionUID = 1192457210091910933L;


  4. Sync(int permits) {

  5. // 设置AQS的状态,即设置许可

  6. setState(permits);

  7. }


  8. final int getPermits() {

  9. // 获取许可置

  10. return getState();

  11. }


  12. // 非公平模式获取共享锁的方法,入参为准备获取的许可数

  13. final int nonfairTryAcquireShared(int acquires) {

  14. for (;;) {// 无循环

  15. // 可用的许可数

  16. int available = getState();

  17. // 剩余的许可数

  18. int remaining = available - acquires;

  19. // 剩余许可数小于0,然后通过cas设置剩余许可数

  20. if (remaining < 0 ||

  21. compareAndSetState(available, remaining))

  22. return remaining;

  23. }

  24. }


  25. // 尝试释放共享锁的方法

  26. protected final boolean tryReleaseShared(int releases) {

  27. for (;;) {// 无限循环

  28. // 当前许可

  29. int current = getState();

  30. // 当前的与准备释放的之和就是之后将会有的许可数

  31. int next = current + releases;

  32. // 溢出的情况

  33. if (next < current) // overflow

  34. throw new Error("Maximum permit count exceeded");

  35. // cas设置将要拥有的许可数

  36. if (compareAndSetState(current, next))

  37. return true;

  38. }

  39. }


  40. // 减少许可数

  41. final void reducePermits(int reductions) {

  42. for (;;) {// 无限循环

  43. // 当前许可数

  44. int current = getState();

  45. // 剩下拥有的许可数为当前许可数减去要减少的许可数

  46. int next = current - reductions;

  47. // 许可数使用溢出

  48. if (next > current) // underflow

  49. throw new Error("Permit count underflow");

  50. // cas设置将要拥有的许可数

  51. if (compareAndSetState(current, next))

  52. return;

  53. }

  54. }

  55. // 释放全部许可

  56. final int drainPermits() {

  57. for (;;) {// 无限循环

  58. // 当前许可

  59. int current = getState();

  60. // 如果当前许可为初始值0或者通过cas设置状态值为0

  61. if (current == 0 || compareAndSetState(current, 0))

  62. return current;

  63. }

  64. }

  65. }

Sync也是基于AQS来实现的,Sync有两个子类,公平版本和非公平版本。

非公平版本:java.util.concurrent.Semaphore.NonfairSync

  1. static final class NonfairSync extends Sync {

  2. private static final long serialVersionUID = -2694183684443567898L;


  3. NonfairSync(int permits) {

  4. // 构造时传入初始的许可数

  5. super(permits);

  6. }

  7. // 尝试获取共享许可

  8. protected int tryAcquireShared(int acquires) {

  9. // 非公平版本获取共享许可

  10. return nonfairTryAcquireShared(acquires);

  11. }

  12. }

公平版本:java.util.concurrent.Semaphore.FairSync

  1. static final class FairSync extends Sync {

  2. private static final long serialVersionUID = 2014338818796000944L;


  3. FairSync(int permits) {

  4. // 传入初始化的许可

  5. super(permits);

  6. }

  7. // 尝试获取共享许可

  8. protected int tryAcquireShared(int acquires) {// 传入需要获取的许可数

  9. for (;;) {// 无限循环

  10. // 公平版本需要先判断当前AQS队列中是否有头节点,也就是是否有比当前节点等待更久的节点

  11. if (hasQueuedPredecessors())

  12. return -1;

  13. // 可用的许可数

  14. int available = getState();

  15. // 剩余可用许可数

  16. int remaining = available - acquires;

  17. // 如果剩余可用小于0或者cas设置剩余可用许可成功,直接返回

  18. if (remaining < 0 ||

  19. compareAndSetState(available, remaining))

  20. return remaining;

  21. }

  22. }

  23. }

公平版本与非公平版本的区别

公平版本在获取许可时需要先通过hasQueuedPredecessors方法判断是否有比当前节点等待更久的节点。

Semaphore

构造方法

  1. public Semaphore(int permits) {

  2. sync = new NonfairSync(permits);

  3. }


  4. public Semaphore(int permits, boolean fair) {

  5. sync = fair ? new FairSync(permits) : new NonfairSync(permits);

  6. }

默认是非公平版本的Sync。

获取许可

java.util.concurrent.Semaphore#acquire()

  1. public void acquire() throws InterruptedException {

  2. sync.acquireSharedInterruptibly(1);

  3. }

java.util.concurrent.Semaphore#acquire(int)

  1. public void acquire(int permits) throws InterruptedException {

  2. if (permits < 0) throw new IllegalArgumentException();

  3. sync.acquireSharedInterruptibly(permits);

  4. }

关于java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly,实际上调用的是AQS的实现,代码如下:

  1. public final void acquireSharedInterruptibly(int arg)

  2. throws InterruptedException {

  3. if (Thread.interrupted())

  4. throw new InterruptedException();

  5. if (tryAcquireShared(arg) < 0)//获取不到许可时,调用doAcquireSharedInterruptibly方法

  6. doAcquireSharedInterruptibly(arg);

  7. }

tryAcquireShared方法根据当前的公平与非公平版本Sync来进行不同的处理,当获取不到许可时,调用doAcquireSharedInterruptibly方法。

java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly代码:

  1. private void doAcquireSharedInterruptibly(int arg)

  2. throws InterruptedException {

  3. // 向AQS队列添加一个SHARED状态的节点

  4. final Node node = addWaiter(Node.SHARED);

  5. boolean failed = true;

  6. try {

  7. for (;;) {// 无限循环

  8. // 获取前置节点

  9. final Node p = node.predecessor();

  10. // 如果前置节点是头节点

  11. if (p == head) {

  12. // 尝试获取共享许可

  13. int r = tryAcquireShared(arg);

  14. if (r >= 0) {

  15. // 成功了则设置头节点并进行传播

  16. setHeadAndPropagate(node, r);

  17. p.next = null; // help GC

  18. failed = false;

  19. return;

  20. }

  21. }

  22. // 判断是否需要在失败时进行park(即等待)

  23. if (shouldParkAfterFailedAcquire(p, node) &&

  24. parkAndCheckInterrupt())

  25. throw new InterruptedException();

  26. }

  27. } finally {

  28. if (failed)

  29. // 取消获取许可

  30. cancelAcquire(node);

  31. }

  32. }

这个方法会在新节点的前置节点是头节点时去尝试获取许可,并在获取成功时进行头节点的设置并进行传播。否则可能会改变waitStatus进行重试,在SIGNAL状态且重试失败时挂起(park)当前节点线程。

java.util.concurrent.Semaphore#tryAcquire()

  1. public boolean tryAcquire() {

  2. return sync.nonfairTryAcquireShared(1) >= 0;

  3. }


  4. public boolean tryAcquire(int permits) {

  5. if (permits < 0) throw new IllegalArgumentException();

  6. return sync.nonfairTryAcquireShared(permits) >= 0;

  7. }

可以看到tryAcquire方法调用的是非公平版本的acquireShared方法,即nonfairTryAcquireShared,入参为1,也就是默认尝试获取许可,且该方法不会阻塞。tryAcquire(int permits)只是将许可数作为一个入参。

释放许可

方法java.util.concurrent.Semaphore#release():

  1. public void release() {

  2. sync.releaseShared(1);

  3. }

释放许可的方法,默认是释放一个许可。

java.util.concurrent.Semaphore#release(int):

  1. public void release(int permits) {

  2. if (permits < 0) throw new IllegalArgumentException();

  3. sync.releaseShared(permits);

  4. }

调用的是Sync类中的releaseShared方法来释放指定数量的许可。

总结

Semaphore主要是用AQS中的state来代表许可,然后锁的获取和释放都是基于state变量和AQS队列的cas操作来实现。源码和功能也都相对简单。使用场景主要是对一个共享资源提供n个可以访问的许可,也就是说只有获取到许可的线程可以进行对这个被保护的资源的访问。


文章转载自开发架构二三事,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论