19.2 信号量Semaphore
之前介绍的锁都是限制只有一个线程可以同时访问一个资源。现实中,资源往往有多个,但每个同时只能被一个线程访问,比如,饭店的饭桌、火车上的卫生间。有的单个资源即使可以被并发访问,但并发访问数多了可能影响性能,所以希望限制并发访问的线程数。还有的情况,与软件的授权和计费有关,对不同等级的账户,限制不同的最大并发访问数。
信号量类Semaphore就是用来解决这类问题的,它可以限制对资源的并发访问数,它有两个构造方法:
1 2
| public Semaphore(int permits) public Semaphore(int permits, boolean fair)
|
fire表示公平,含义与之前介绍的是类似的,permits表示许可数量。
Semaphore的方法与锁是类似的,主要的方法有两类,获取许可和释放许可,主要方法有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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()
|
我们看个简单的示例,限制并发访问的用户数不超过100,如代码清单19-2所示。
代码清单19-2 Semaphore应用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class AccessControlService { public static class ConcurrentLimitException extends RuntimeException { private static final long serialVersionUID = 1L; } private static final int MAX_PERMITS = 100; private Semaphore permits = new Semaphore(MAX_PERMITS, true); public boolean login(String name, String password) { if(! permits.tryAcquire()) { throw new ConcurrentLimitException(); } return true; } public void logout(String name) { permits.release(); } }
|
代码比较简单,就不赘述了。需要说明的是,如果我们将permits的值设为1,你可能会认为它就变成了一般的锁,不过,它与一般的锁是不同的。一般锁只能由持有锁的线程释放,而Semaphore表示的只是一个许可数,任意线程都可以调用其release方法。主要的锁实现类ReentrantLock是可重入的,而Semaphore不是,每一次的acquire调用都会消耗一个许可,比如,看下面的代码段:
1 2 3 4
| Semaphore permits = new Semaphore(1); permits.acquire(); permits.acquire(); System.out.println("acquired");
|
程序会阻塞在第二个acquire调用,永远都不会输出“acquired”。
信号量的基本原理比较简单,也是基于AQS实现的,permits表示共享的锁个数,acquire方法就是检查锁个数是否大于0,大于则减一,获取成功,否则就等待,release就是将锁个数加一,唤醒第一个等待的线程。