简介
计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。计数信号量还可以用来实现某种资源池,或者对容器施加边界。
Semaphore中管理着一组虚拟许可(permit),许可的初始数量可以通过构造函数来指定,在执行操作时可以首先获得许可(只要还有剩余的许可),并在使用之后释放许可。如果没有剩余许可,那么获取许可的acquire操作将阻塞直到有许可(或者被中断、或者超时)。通过release方法释放一个许可信号量。计数信号量的一种简化形式是二值信号量,即初始值为1的Semaphore,二值信号量可以用做互斥体(mutex),并具备不可重入的加锁语义:谁拥有了这个唯一的许可,谁就拥有了互斥锁。
Semaphore源码详解
Semaphore的类图如下:
从上图中可以看出,Semaphore的实现依赖于其内部类Sync,Sync类继承自AQS,同时它还有两个子类NonfairSync非公平同步器以及FairSync公平同步器。
Semaphore提供了两个构造函数:
public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
Semaphore的默认构造方法Semaphore(int permits)接受一个整形的数字,表示可用的许可证数量,它也对应于AQS中的同步状态。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。通过该构造函数创建的sync默认是非公平的。
Semaphore的另一个构造方法Semaphore(int permits, boolean fair)除了可以设置许可证数量之外,还可以控制同步器的公平性。
信号量获取
Semaphore提供了acquire()方法来获取许可(即信号量):
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
acquireSharedInterruptibly(int)是AQS实现的模板方法,该方法会以共享响应中断的方式获取同步状态:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
tryAcquireShared(int)是由子类实现的方法,对于Semaphore来说,因为Semaphore内部有公平和非公平两种自定义同步器,所以tryAcquireShared(int)方法的实现有两种:
公平锁
公平锁对应的tryAcquireShared(int)方法如下:
protected int tryAcquireShared(int acquires) { for (;;) { // 判断当前节点是否为同步队列的首节点,保证锁的获取是公平的 if (hasQueuedPredecessors()) return -1; // 获取同步状态,即剩余许可证的数量 int available = getState(); // 计算新的同步状态,即再分配acquires个许可证之后的剩余值 int remaining = available - acquires; // 以CAS操作来设置同步状态,即修改许可证的数量 if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
非公平锁
非公平锁对应的tryAcquireShared(int)方法如下:
protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); }
它会调用SyncnonfairTryAcquireShared(int)方法:
final int nonfairTryAcquireShared(int acquires) { for (;;) { // 获取同步状态,即剩余许可证的数量 int available = getState(); // 计算新的同步状态,即再分配acquires个许可证之后的剩余值 int remaining = available - acquires; // 以CAS操作来设置同步状态,即修改许可证的数量 if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
从上述源码可以看出,公平与非公平的差别主要是有没有调用hasQueuedPredecessors()方法,该方法是用来判断同步队列中当前节点是否有前驱节点。如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此要先等待前驱线程获取锁,以保证公平性。
信号量释放
Semaphore提供了release()方法来释放许可:
public void release() { sync.releaseShared(1); }
releaseShared(int)是AQS实现的模板方法:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
tryReleaseShared(int)是由Sync子类实现的方法:
protected final boolean tryReleaseShared(int releases) { for (;;) { // 获取同步状态,即许可证的剩余量 int current = getState(); // 计算新的同步状态,即线程释放releases个许可证后的数量 int next = current + releases; if (next < current) // overflow throw new Error("Maximum permit count exceeded"); // 以CAS操作修改同步状态,即修改许可证的数量 if (compareAndSetState(current, next)) return true; } }
Semaphore还提供了一些其他方法:
- int availablePermits():返回此信号量中剩余许可证数
- int getQueueLength():返回正在等待获取许可证的线程数
- boolean hasQueuedThreads():判断是否有线程正在等待获取许可证
- void reducePermits(int reductions):减少reductions个许可证,是protected方法
- Collection<Thread> getQueuedThreads():返回所有等待获取许可证的线程集合,是protected方法
应用程序示例
我们看一个Semaphore的应用示例:
public class SemaphoreTest { // 自定义工作线程 private static class Worker extends Thread { private CountDownLatch countDownLatch; private Semaphore semaphore; public Worker(CountDownLatch countDownLatch, Semaphore semaphore) { this.countDownLatch = countDownLatch; this.semaphore = semaphore; } @Override public void run() { super.run(); boolean hasAcquire = false; try { countDownLatch.await(); semaphore.acquire(); hasAcquire = true; System.out.println(Thread.currentThread().getName() + "开始执行"); // 工作线程开始处理,这里用Thread.sleep()来模拟业务处理 Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "执行完毕"); } catch (Exception e) { e.printStackTrace(); } finally { if (hasAcquire) { semaphore.release(); } } } } public static void main(String[] args) { int threadCount = 10; int permitCount = 3; CountDownLatch countDownLatch = new CountDownLatch(1); Semaphore semaphore = new Semaphore(permitCount); for (int i = 0; i < threadCount; i++) { Worker worker = new Worker(countDownLatch, semaphore); worker.start(); } countDownLatch.countDown(); } }
运行结果(不唯一):
Thread-1开始执行 Thread-9开始执行 Thread-0开始执行 Thread-9执行完毕 Thread-1执行完毕 Thread-0执行完毕 Thread-3开始执行 Thread-2开始执行 Thread-4开始执行 Thread-3执行完毕 Thread-2执行完毕 Thread-4执行完毕 Thread-6开始执行 Thread-5开始执行 Thread-7开始执行 Thread-7执行完毕 Thread-5执行完毕 Thread-6执行完毕 Thread-8开始执行 Thread-8执行完毕
Semaphore有3个许可,但是有10个线程要执行,从执行结果中可以看出,每次都是3个线程一组开始执行,也就是每次只能有3个线程获取许可。
相关博客
AbstractQueuedSynchronizer同步队列详解
AbstractQueuedSynchronizer共享式同步状态获取与释放
参考资料
方腾飞:《Java并发编程的艺术》
Doug Lea:《Java并发编程实战》