1. Semaphore应用
一般用于流控。比如有一个公共资源,多线程都可以访问时,可以用信号量做限制。
连接池,内部的链接对象有限,每当有一个线程获取连接对象时,对信号量-1,当这个线程归还资源时对信号量+1。 如果线程拿资源时,发现Semaphore内部的资源个数为0,就会被阻塞。
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
// 声明信号量
Semaphore semaphore = new Semaphore(1);
// 能否去拿资源
semaphore.acquire();
// 拿资源处理业务
System.out.println("main");
// 归还资源
semaphore.release();
}
2. Semaphore核心源码分析
2.1 有参构造
Semaphore有公平和非公平两种竞争资源的方式。通过参数fair控制
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
// 设置资源个数,State其实就是信号量的资源个数
Sync(int permits) {
setState(permits);
}
2.2 acquire
// acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// 公平
protected int tryAcquireShared(int acquires) {
for (;;) {
// 公平方式,先好看队列中有没有排队的,有排队的返回-1,执行doAcquireSharedInterruptibly去排队
if (hasQueuedPredecessors())
return -1;
// 那state
int available = getState();
// remaining = 资源数 - 1
int remaining = available - acquires;
// 如果资源不够,直接返回-1
if (remaining < 0 ||
// 如果资源够,执行CAS,修改state
compareAndSetState(available, remaining))
return remaining;
}
}
// 非公平
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
2.3 release
// 公平和非公平共用
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 唤醒在AQS中排队的Node,去竞争资源
doReleaseShared();
return true;
}
return false;
}
// 信号量实现的归还资源
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 拿state
int current = getState();
// state + 1
int next = current + releases;
// 资源最大值,再+1,变为负数
if (next < current)
throw new Error("Maximum permit count exceeded");
// CAS 改一手
if (compareAndSetState(current, next))
return true;
}
}
2.4 AQS中PROPAGATE状态
JDK1.5中,使用信号量时,可能会造成在有资源的情况下,后继节点无法被唤醒。
在JDK1.8中,问题被修复,修复方式就是追加了PROPAGATE节点状态来解决。
共享锁在释放资源后,如果头节点为0,无法确认真的没有后继节点。如果头节点为0,需要将头节点的状态修改为-3,当最新拿到锁资源的线程,查看是否有后继节点并且为共享锁,就唤醒排队的线程