背景:在编程过程中,我们使用线程池来对线程资源进行管理(创建和回收等),在编程中使用多线程的同时也需要对线程资源进行一定的保护;Semaphore 作为信号量控制器,可以用来控制资源的访问,以达到想要的限流;
1 使用:
public class SemaphoreUtil {
// 声明最大的资源
private static final int MAX_AVAILABLE = 100;
// 声明 Semaphore ,第二个参数用来控制锁的公平心,true 为公平锁,false 非公平锁
private static final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public static void main(String[] args) {
boolean hasLock1 = false;
try {
// 获取锁资源
available.acquire();
hasLock1 = true;
// doSomeThing() // 获取资源成功,处理业务逻辑
} catch (InterruptedException e) {
} finally {
if (hasLock1) {
// 最终释放获取到的资源
available.release();
}
}
// thread
boolean hasLock = false;
try {
if (available.tryAcquire(100, TimeUnit.MILLISECONDS)) {
// 在规定的时间内获取到了资源
hasLock = true;
// doSomeThing()
} else {
// no lock to used
}
} catch (InterruptedException e) {
} finally {
if (hasLock) {
// 最终释放获取到的资源
available.release();
}
}
}
}
其它方法概览:
// 创建具有给定的许可数和非公平的公平设置的 Semaphore。
Semaphore(int permits)
// 创建具有给定的许可数和给定的公平设置的 Semaphore。
Semaphore(int permits, boolean fair)
// 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
void acquire()
// 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。
void acquire(int permits)
// 从此信号量中获取许可,在有可用的许可前将其阻塞。
void acquireUninterruptibly()
// 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。
void acquireUninterruptibly(int permits)
// 返回此信号量中当前可用的许可数。
int availablePermits()
// 获取并返回立即可用的所有许可。
int drainPermits()
// 返回一个 collection,包含可能等待获取的线程。
protected Collection<Thread> getQueuedThreads()
// 返回正在等待获取的线程的估计数目。
int getQueueLength()
// 查询是否有线程正在等待获取。
boolean hasQueuedThreads()
// 如果此信号量的公平设置为 true,则返回 true。
boolean isFair()
// 根据指定的缩减量减小可用许可的数目。
protected void reducePermits(int reduction)
// 释放一个许可,将其返回给信号量。
void release()
// 释放给定数目的许可,将其返回到信号量。
void release(int permits)
// 返回标识此信号量的字符串,以及信号量的状态。
String toString()
// 仅在调用时此信号量存在一个可用许可,才从信号量获取许可。
boolean tryAcquire()
// 仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。
boolean tryAcquire(int permits)
// 如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。
boolean tryAcquire(int permits, long timeout, TimeUnit unit)
// 如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。
boolean tryAcquire(long timeout, TimeUnit unit)
2 获取和释放资源的流程:
2.1 new Semaphore(MAX_AVAILABLE, true):
public Semaphore(int permits, boolean fair) {
// true 公平锁(FIFO),false 非公平锁,允许插队抢占资源(可以提高效率/吞吐量)
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
Sync(int permits) {
// 设置限制资源的数
setState(permits);
}
protected final void setState(int newState) {
state = newState;
}
2.2 available.acquire():
public void acquire() throws InterruptedException {
// 获取一个共享资源
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();// 线程中断则抛出异常
// 如果现在的资源已经被占用完毕则为ture 否则false
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
// 没有获取到资源的现车需要加入到AQS队列中,并挂起当前线程释放CPU
}
protected int tryAcquireShared(int acquires) {
for (;;) {// 自旋
// 因为是公平锁不允许插队,如果发现AQS中已经存在阻塞的线程,则直接返回-1
if (hasQueuedPredecessors())
return -1;
// 当前AQS 没有节点,获取当前剩余的资源数
int available = getState();
// 本次获取资源后剩余的资源数
int remaining = available - acquires;
// 如果剩余资源数小于0(资源被占完)或者 cas替换剩余量成功
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;// 返回剩余量
}
}
/** 获取资源失败加入到AQS阻塞队列中
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 将当前节点加入到AQS 双向链表中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
// 如果当前节点的前置节点为head 节点 (FIFO)表明当前节点线程可以去尝试获取资源
if (p == head) {
// 获取剩余资源数量
int r = tryAcquireShared(arg);
if (r >= 0) {// 如果有可用资源
// 链式的唤醒AQS 中的节点依次去获取资源
setHeadAndPropagate(node, r);
// 获取资源成功后,从双向链表中移除改节点,并返回
p.next = null; // help GC
failed = false;
return;
}
}
// 如果改线程没有获取到资源,则阻塞改线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();// 如果线程中断则直接抛出异常
}
} finally {
if (failed)
cancelAcquire(node);// 清除无效的节点
}
}
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() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
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 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
/** 节点唤醒
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);// unpark()唤醒线程继续业务的执行
}
2.3 available.release():
public void release() {
// 释放资源
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {// 释放资源操作
// 资源释放成功则将AQS最先进入的有效节点进行唤醒
doReleaseShared();
return true;
}
return false;
}
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;
}
}
3 总结:
在Semaphore 中我们使用acquire() 或者tryAcquire() 来获取资源,在获取到资源处理业务之后通过release() 方法进行资源的释放;
4 参考:
4.1 Semaphore的简介及应用场