Semaphore是Java中一个同步工具类,用于控制同时访问某个资源的线程数。
整体架构
构造函数
Semaphore支持公平锁和非公平锁,默认非公平锁 构造函数如下
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
FairSync和NonfairSync是公平锁和非公平锁的实现,两者的区别在于获取共享资源的实现不一致。
NonfairSync的实现如下
protected int tryAcquireShared(int acquires) {
//父类Sync中的实现
return nonfairTryAcquireShared(acquires);
}
nonfairTryAcquireShared
是一个非公平的尝试获取共享许可的方法。它的作用是尝试获取acquires
个共享许可,返回当前剩余的许可数量
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
//如果<0直接返回,或者利用CAS将state设置成剩余许可证数量
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
FairSync实现
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;
}
}
第4行和分公平锁不一样hasQueuedPredecessors
实现如下
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//如果当前线程在AQS的等待队列中有前驱节点,则该方法返回true;当前节点就是前驱节点或者当前节点为空否则返回false。
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
获取许可
着重分析获取一个许可acquire()
,其他主要介绍不同之处
acquire() 源码分析
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquireSharedInterruptibly
是AQS中方法,共享方式获取,遇到其他线程中断当前线程返回InterruptedException
,获取到许可直接返回,获取不到等待
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//第4行判断的同时会清除中断标记
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
// 获取不到许可,添加队列并等待
doAcquireSharedInterruptibly(arg);
}
继续doAcquireSharedInterruptibly
,这个方法是实现共享模式下的可中断获取同步许可,如果当前线程中断,将会抛出中断状态
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//1.添加共享模式Node节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//2.获取前驱节点
final Node p = node.predecessor();
//如果是头节点
if (p == head) {
//3.尝试获取许可
int r = tryAcquireShared(arg);
if (r >= 0) {
//4.把新增的节点设置头结点,并换新后面节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//5.获取失败判断是否需要挂起
if (shouldParkAfterFailedAcquire(p, node) &&
//6.挂起检查是否中断
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
//7.失败取消获取许可
cancelAcquire(node);
}
}
先看addWaiter
,主要是新增一个Node节点,并放到尾结点,并替换当前的tail
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 同步队列为空或者上述CAS设置失败,入队
enq(node);
return node;
}
//入队操作,包括初始化双向队列
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//注意头结点head是一个无含义的哨兵节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
tryAcquireShared
方法前面已经介绍,需要继承AQS的同步类进行实现
setHeadAndPropagate
是在当前节点已经获取许可后,剩余的许可r
仍然大于0,尝试唤醒后面节点
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//设置头结点
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//唤醒头结点
doReleaseShared();
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
//设置成PROPAGATE状态,表示需要在之后继续传播释放信号
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//s节点可能已经取消CANCELLED
if (s == null || s.waitStatus > 0) {
s = null;
//从尾结点开始向前找第一个SIGNAL PROPAGATE节点进行唤醒
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
shouldParkAfterFailedAcquire
获取失败判断是否需要挂起,源码解释已经很详细,主要是前置节点pred如果是SINGAL状态,则当前线程可以park;如果是取消状态,需要将取消队列移除,并重新连接队列,doAcquireSharedInterruptibly下一次for循环进行判断;如果是PROPAGATE或者为0,需要设置为SINGAL,下次for就可以park
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
需要等待比较简单parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
//唤醒后判断中断标记
return Thread.interrupted();
}
失败需要取消许可,目前failed为true可能出现在中断异常
if (failed)
cancelAcquire(node);
cancelAcquire
主要是将node节点设置为CANCELD,然后从同步队列中移除
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
其他获取许可方法
不中断获取一个许可acquireUninterruptibly
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
//唤醒重设中断标记
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//中断设置标记
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tryAcquire
不等待,直接返回标记
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
tryAcquire(long timeout, TimeUnit unit)
等待一段时间返回是否获取标记
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
//唤醒后获取许可失败,直接返回false
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
//时间很短,直接for循环,线程状态切换消耗大于循环
nanosTimeout > spinForTimeoutThreshold)
//线程等待一段时间
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
加锁流程
比较懒,直接取自与 blog.csdn.net/weixin_4631…
释放许可
release
,整体比较简单,直接贴源码
public void release() {
sync.releaseShared(1);
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
//Semaphore中应该走不到
return false;
}
//Sync中实现
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
doReleaseShared
上面已经贴过源码,不再赘述
解锁流程
直接取自与 blog.csdn.net/weixin_4631…
其他方法
public int availablePermits()
获取当前许可数量
public int drainPermits()
用于获取并重置当前 Semaphore 对象的可用许可数
JDK中实现AQS简介
同步工具 | 与AQS关联 | 详细介绍 |
---|---|---|
AQS原理讲解 | AQS原理介绍 | 并发-AQS原理讲解 |
ReentrantLock | 使用AQS保存锁重复持有的次数。当一个线程获取锁时,ReentrantLock记录当前获得锁的线程标识,用于检测是否重复获取,以及错误线程试图解锁操作时异常情况的处理。 | AQS之Reentrantlonk源码解读 |
Semaphore | 使用AQS同步状态来保存信号量的当前计数。tryRelease会增加计数,acquireShared会减少计数。 | Semaphore 源码分析以及AQS共享加解锁 |
CountDownLatch | 在多线程并发执行任务时,有时需要让某些线程等待某些条件达成后再开始执行,这时就可以使用CountDownLatch来实现 | CountDownLatch 源码分析 |
ThreadPoolExecutor | 创建线程池中的工作线程worker继承AQS,实现独占资源 | 参考 并发-AQS之ThreadPoolExecutor源码解读(一) |
CyclicBarrier | 多个线程等待彼此达到一个共同的屏障点,然后同时继续执行。 | 并发-AQS之CyclicBarrier源码解读 |
ReentrantReadWriteLock | 可重入读写锁,它允许多个线程同时读取一个共享资源,但只允许一个线程写入该共享资源。 | 参考 并发-AQS之ReentrantReadWriteLock源码解读(一) |