Semaphore信号量
Semaphore也是基于AQS实现的,AQS源码解析过程:AQS源码解析
构造方法
参数 permits 指的就是state值,Semaphore 默认使用的是非公平锁,但是也可以传fair参数使用公平锁
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
成员方法
获取锁
除了 acquire() 获取锁之外,还可以传入信号量获取 acquire(int permits),其实现和前者大同小异;
还有就是通过非阻塞 tryAcquire() 方法获取锁,即尝试获取锁,若未获取成功则直接返回结果,当然也有传入信号量 / 时间参数的非阻塞方法 tryAcquire(int permits) / tryAcquire(int permits, long timeout, TimeUnit unit) / tryAcquire(long timeout, TimeUnit unit),实现的逻辑也都差不多
以下主要以 acquire() 为例子
// 阻塞式获取锁
public void acquire() throws InterruptedException {
// 调用AQS的acquireSharedInterruptibly方法
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
// 如果遇到线程中断则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
public boolean tryAcquire() {
// 默认传的信号量为1
return sync.nonfairTryAcquireShared(1) >= 0;
}
final int nonfairTryAcquireShared(int acquires) {
// 自旋,直到获取锁
for (;;) {
// 获取定义的信号量值
int available = getState();
// 获取剩余的信号量值
int remaining = available - acquires;
// 若剩余信号量值小于0,说明不允许其他线程抢占资源,都得进入等待队列中排队
// 若剩余信号量值大于0,并且state值cas成功,则获取资源成功
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
释放锁
释放锁通常是调用release方法;也和获取锁的方法一样,有着信号量和时间的重载方法
public void release() {
// 也是调用AQS中的releaseShared方法,默认信号量也为1
sync.releaseShared(1);
}
// AQS中的方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 唤醒线程
doReleaseShared();
return true;
}
return false;
}
// Semaphore重写AQS的方法tryReleaseShared
protected final boolean tryReleaseShared(int releases) {
// 自旋,直至释放资源成功
for (;;) {
// 获取当前的总信号量
int current = getState();
// 释放资源,总信号量+1
int next = current + releases;
if (next < current)
throw new Error("Maximum permit count exceeded");
// 自旋修改总信号量
if (compareAndSetState(current, next))
return true;
}
}
总结
其实 Semaphore 的底层实现很简单明了,和 countdownlatch 差不多都是围绕一个 int值 去判断能否 获取/释放 资源
案例
假设每一个线程都代表一辆车,每次有一辆车停车,就会显示剩余车位减一,每次有一辆车出去,显示屏上就显示剩余车位加1,当显⽰屏上的剩余车位为0时,停车场⼊⼝的栏杆就不会再打开,车辆就⽆法进⼊停车场了,直到有⼀辆车从停车场出去为⽌
public class Test {
// 初始化
static Semaphore semaphore = new Semaphore(5);
static AtomicInteger sortNum = new AtomicInteger(0);
private static ExecutorService executor = new ThreadPoolExecutor(10, 10, 6000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
private static void parkCar() {
int i = sortNum.addAndGet(1);
try {
// 1号汽车进来尝试获取资源,发现信号量为5,减去自身1,大于0成功获取资源
// 2号汽车也进来尝试获取资源,发现信号量为4,减去自身1,大于0成功也获取资源
// 3号汽车也进来尝试获取资源,发现信号量为3,减去自身1,大于0成功也获取资源
// ....
// 直到6号汽车进来获取资源的时候,发现信号量为0,减去自身1,小于0,这时获取锁不成功,需要插入等待队列进行排队
semaphore.acquire();
System.out.println(i + "号汽车驶入停车场");
// 1号汽车停车成功,花费3s,再这段时间里其实已经有很多辆车停进停车场了
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 停车成功,开始释放资源,这时可能是释放2号/3号/5号...的资源
// 总之释放的时候会将总信号量+1,并且唤醒等待队列中的汽车,在非公平的情况下可能是6号/8号/12号...
// 这时唤醒的车辆又可以重新尝试获取资源进入停车场了
semaphore.release();
System.out.println(i + "号汽车驶出停车场");
}
}
public static void main(String[] args) throws Exception {
// 模拟多线程访问
for (;;) {
executor.execute(() -> parkCar());
// 假设每辆车进入之后需要花费100ms
Thread.sleep(100);
}
}
}