版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/fei20121106/article/details/83268604
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞
一、使用示例
// 创建一个计数阈值为5的信号量对象
// 只能5个线程同时访问
Semaphore semp = new Semaphore(5);
try {
// 申请许可
semp.acquire();
try {
// 业务逻辑
} catch (Exception e) {
} finally {
// 释放许可
semp.release();
}
} catch (InterruptedException e) {
二、总体结构
public class Semaphore implements java.io.Serializable {
private final Sync sync;
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
//[Lock接口]获取锁. 成功则向下运行,失败则阻塞
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
//[Lock接口]可中断地获取锁,在当前线程获取锁的过程中可以响应中断信号
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
//[Lock接口]尝试非阻塞获取锁,调用方法后立即返回,成功返回true,失败返回false
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
//[Lock接口]在超时时间内获取锁,到达超时时间将返回false,也可以响应中断
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
//[Lock接口]释放锁
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
//[Lock接口]获取等待通知组件实现信号控制,等待通知组件实现类似于Object.wait()方法的功能
public Condition newCondition() {
return this.sync.newCondition();
}
}
通过代码我们可以看到,基本全部的业务都是交给 Sync去实现的
- Sync类是AQS的子类,而NonfairSync和FairSync是Sync的子类。
- 公平锁和非公平锁的区别就在于获取锁时候的逻辑略有不同,其他操作都是一样的,因此公用的操作都放在Sync类里,NonfairSync和FairSync里只是实现自己的tryAcquireShared(int acquires)方法。
值得注意的是:
通常而言”AQS里的state在重入锁里代表线程重入的次数,state=1代表重入锁当前已被某个线程独占,这个线程每重入一次,state++“,但是在Semaphore中:
- state在AQS被实例化时构建为 允许共享的次数
- state>0 表示 还可以被共享占有
三、acquire()和release()大致流程
acquire()获取锁的主要流程如下:
- 首先,Semaphore 的 acquire() 方法会调用其内部成员变量sync的 acquireShared() 方法;
- 其次,sync的非公平锁NonfairSync或公平锁FairSync实现了父类AbstractQueuedSynchronizer的相关钩子方法
3.1 AQS的实现
我们回忆一下AQS需要重写的钩子方法:
方法名称 | 描述 |
---|---|
boolean tryAcquire(int arg) | 独占式尝试获取同步状态(通过CAS操作设置同步状态),如果成功返回true,反之返回false |
boolean tryRelease(int arg) | 独占式释放同步状态,成功返回true,失败返回false。 |
int tryAcquireShared(int arg) | 共享式的获取同步状态,返回大于等于0的值,表示获取成功,反之失败。 |
boolean tryReleaseShared(int arg) | 共享式释放同步状态,成功返回true,失败返回false。 |
boolean isHeldExclusively() | 判断同步器是否在独占模式下被占用,一般用来表示同步器是否被当前线程占用 |
我们来看下Sync对其的实现,可以看出Sync
中:
- 实例化时,重置 state的值
- 重写了
tryReleaseShared
共享式释放同步状态 - 并没有重写
tryAcquireShared(int arg)
共享式尝试获取同步状态方法, 而是交给子类去实现 - 新建了
nonfairTryAcquireShared()
,这其实是个同步状态获取方法,在”非公平模式“的子类中被调用 - 一系列的Utils方法
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
/**************1. 实例化时,重置 state的值*********/
Sync(int permits) {
setState(permits);
}
/**************2. 重写了`tryRelease`独占式释放同步状态*********/
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // 释放超支
throw new Error("Maximum permit count exceeded");
//共享式锁的【更新锁状态】不能通过setState,而是通过CAS操作。
//这是由于独享式锁释放时肯定是单线程模型的,而共享式的则可能是多线程模型
if (compareAndSetState(current, next))
return true;
}
}
/******3. 并没有重写`tryAcquire(int arg)`独占式尝试获取同步状态方法, 而是交给子类去实现***********/
/******4. 新建了`nonfairTryAcquire()`,这其实是个同步状态获取方法,在”非公平模式“的子类中被调用*****/
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
/**************5. 一系列的Utils方法********************/
final int getPermits() {
return getState();
}
//自旋式减少 共享信号
//Semaphore还可以减小许可数量的方法,该方法可以用于用于当资源用完不能再用时,这时就可以减小许可证
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
//自旋式重置 共享信号为0
//Semaphore还可以一次将剩余的许可数量全部取走,返回剩余数量
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
3.1.1 非公平锁
- 通过基于CAS操作的compareAndSetState(available, remaining))方法,试图修改当前锁的状态
- 这个上来就CAS的操作也是非公共锁的一种体现,CAS操作成功的话,则将当前线程设置为该锁的唯一拥有者。
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || // >0时表示有可用资源,才进行CAS竞争锁
compareAndSetState(available, remaining))
return remaining;
}
}
}
3.1.2 公平锁
我们可以发现,这段代码与上面的 nonfairTryAcquire方法就只多了一句代码,而就是这一句代码就实现了公平锁。
hasQueuedPredecessors()方法判断同步队列中是否有更早开始等待锁的线程。如果有,则直接返回。
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}