信号量Semaphore是一个控制访问多个共享资源的计数器,和CountDownLatch一样,其本质上是一个“共享锁”。
Semaphore介绍
Semaphore在API是这么介绍的:
一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前阻塞每一个acquire(),然后在获得该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
Semaphore通常用于控制当前访问某些资源的线程个数,并提供了同步机制
举个简单的例子来阐述Semaphore:
假如有这么一个场景,一个厕所有5个坑位,刚开始全部空着,来了3个人,占了3个坑,然后又来了3个人,这个时候由于只有2个坑位,所以只能2个人,另外一个人只能候着,直到坑位空出来。包括后面来的人,也必须在外面候着。当坑位空着了,另外等待的人可以随机进去还是按照先来后到的顺序进去,这取决于构造Semaphore对象传入的参数选项。
从程序的角度来看,厕所坑位就相当于信号量Semaphore,其中许可数为5,上厕所的人就相当于线程,当进来一个人许可数就减1,当没有了坑位(许可就为0了)
信号量是一个非负整数(大于等于1),当一个线程访问共享资源时,它必须先获取Semaphore,当Semaphore>0时,获取该资源并使Semaphore-1,若Semaphore为0,则表示全部的共享资源已经被其他线程占用,必须等待其他线程释放资源。
Semaphore分析
从Semaphore源码来看,其内部结构包含了FairSync(公平锁)和NonfairSync(非公平锁),基础内部类Sync,其中Sync继承AQS(AQS很重要)。
构造函数
Semaphore提供2个构造函数:
- Semaphore(int permits),创建具有给定的许可数和非公平的公平设置的 Semaphore
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
- Semaphore(int permits, boolean fair),创建具有给定的许可数和给定的公平设置的 Semaphore
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
Semaphore默认选择非公平锁,当信号量Semaphore=1时,它可以当做互斥锁使用。其中0,1就相当于它的状态,当为1时表示其他线程可以获取;当为0时,即其他线程必须等待。
信号量获取
Semaphore提供了acquire()方法来获取一个许可
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
内部调用AQS的acquireSharedInterruptibly(int arg),该方法以共享模式获取同步状态:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
信号量释放
Semaphore提供release()来释放许可
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
内部调用的AQS中的releaseShared():
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
代码示例
就拿上面的厕所坑位来举例:
public class SemaphoreDemo {
public static void main(String[] args) throws Exception{
ExecutorService service = Executors.newCachedThreadPool();
//5个坑位
final Semaphore semaphore = new Semaphore(5);
//10个人排队
for (int i = 0; i < 10; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("线程" + Thread.currentThread().getName() + "进入,当前已有" + (5 - semaphore.availablePermits()) + "个并发");
Thread.sleep((long) (Math.random()*10000));
System.out.println("线程" + Thread.currentThread().getName() + "即将离开");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
}
}
运行结果如下:
线程pool-1-thread-2进入,当前已有2个并发
线程pool-1-thread-1进入,当前已有2个并发
线程pool-1-thread-3进入,当前已有3个并发
线程pool-1-thread-4进入,当前已有4个并发
线程pool-1-thread-5进入,当前已有5个并发
线程pool-1-thread-2即将离开
线程pool-1-thread-6进入,当前已有5个并发
线程pool-1-thread-3即将离开
线程pool-1-thread-7进入,当前已有5个并发
线程pool-1-thread-4即将离开
线程pool-1-thread-8进入,当前已有5个并发
线程pool-1-thread-5即将离开
线程pool-1-thread-9进入,当前已有5个并发
线程pool-1-thread-9即将离开
线程pool-1-thread-10进入,当前已有5个并发
线程pool-1-thread-7即将离开
线程pool-1-thread-8即将离开
线程pool-1-thread-10即将离开
线程pool-1-thread-1即将离开
线程pool-1-thread-6即将离开