0%

Java 同步和协作工具类(二):信号量 Semaphore

1. 信号量类 Semaphore 的作用是

  • 限制对资源的并发访问数

2. 信号量类 Semaphore 的使用场景是

  • 现实中,资源往往有多个,但每个同时只能被一个线程访问。比如,饭店的饭桌、火车上的卫生间
  • 有的单个资源即使可以被并发访问,但并发访问次数多了可能影响性能,所以希望限制并发访问的线程数
  • 还有,与软件的授权计费有关,对不同等级的账户,限制不同的最大并发访问数

3. Semaphore 的构造方法

  • public Semaphore(int permits)/public Seamphore(int permits, boolean fair)
  • fair 表示公平;permits 表示许可数量

4. Semaphore 的主要方法有

  • public void acquire() throws InterruptedException:阻塞获取许可
  • public void acquireUninterruptibly():阻塞获取许可,不响应中断
  • public void acquire(int permits) throws InterruptedException:批量获取多个许可
  • public void acquireUninterruptibly(int permits):批量获取多个,不响应中断
  • public boolean tryAcquire():尝试获取
  • public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException:限定等待时间获取
  • public void release():释放许可

5. 写一个信号量类 Semaphore 的 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AccessControlService {
private static final int MAX_PERMITS = 100;
private Semaphore permits = new Semaphore(MAX_PERMITS, true);

public static class ConcurrentLimitException extends RuntimeException {
private static final long serialVersionUID = 1L;
}

public boolean login(String name, String password) {
if(!permits.tryAcquire()) {
//同时登录用户数超过限制
throw new ConcurrentLimitException();
}
//……其他验证
return true;
}

public void logout(String name) {
permits.release();
}
}

6. 如果设置 permits 的数量为 1,那 Semaphore 和一般的锁的区别是

  • 一般锁只能由持有锁的线程释放,而 Semaphore 表示的只是一个许可数,任意线程都可以调用其 release() 方法

  • 主要的锁实现类 ReentrantLock 是可重入的;而 Semaphore 不是可重入的,每一次的 acquire() 调用都会消耗一个许可

  • 举例,下面程序会阻塞在第二个 acquire() 调用,永远都不会输出 acquire

    1
    2
    3
    4
    Semaphore permits = new Semaphore(1);
    permits.acquire();
    permits.acquire();
    System.out.println("acquired");

7. 信号量 Semaphore 的基本原理

  • 原理比较简单,也是基于 AQS 实现
  • AQS:并发工具 ReentrantReadWriteLockSemaphoreCountDownLatch,它们的实现有很多类似的地方。为了复用代码,Java 提供了一个抽象类 AbstractQueuedSynchronizer,简称 AQS,简化了并发工具的实现
  • permits 表示共享的锁个数
  • acquire() 方法就是检查锁个数是否大于 0,大于则减一,获取成功,否则就等待
  • release() 方法就是将锁个数加一,唤醒第一个等待的线程
-------------------- 本文结束感谢您的阅读 --------------------