用于控制同时访问某个资源的线程数量,默认非公平
可以用于限制对共享资源的并发访问量,以控制系统的流量。
@Slf4j
public class SemaphoreDemo {
private static Semaphore semaphore = new Semaphore(2);
private static Executor executor = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
executor.execute(() -> { getProductInfo();});
}
}
private static String getProductInfo(){
try {
// 获取许可证
semaphore.acquire();
log.info("业务代码进行操作");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
// 释放许可证
log.info("业务代码执行结束,释放许可证");
semaphore.release();
}
return "执行业务代码结束";
}
private static String getProductInfo2(){
try {
if (!semaphore.tryAcquire()){
log.error("加载失败,请稍后重试");
return "加载失败,请稍后重试";
}
log.info(Thread.currentThread() + " 正常执行业务代码");
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 释放许可证
semaphore.release();
}
return "执行代码结束";
}
}
在实例一个Semaphore的时候,默认是非公平的,且传参一个许可证数量,即对AbstractQueuedSynchronizer.state参数设置一个初始值
其中继承关系如下
其中acquire方法的底层实现
公平锁中
相较于非公平锁,多了一个是否存在队列判断。
判断remaining许可证是否还有,如果remaining > 0此种情况代表还有可用许可证,并尝试CAS执行,执行成功返回remaining。
如果tryAcquireShared(arg) >= 0表示当前线程已经获取到许可证,此时结束执行。
否则执行doAcquireSharedInterruptibly(arg)方法
// 这里的mode参数上游传来的参数Node.SHARED,是个共享的node
private Node addWaiter(Node mode) {
// 根据当前线程实例一个Node
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 如果pred不为null,先将node的上节点设置,然后执行CAS操作将当前节点设置为尾节点并将上一节点的下节点设置为当前node
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果不存在队列,此时执行enq方法
enq(node);
return node;
}
enq方法,创建一个阻塞队列,并设置一个非空头结点,将当前node依次往后放入队列中
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 同样,先关联上一节点,之后执行CAS操作成功之后再将上一节点的next设置为当前节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
shouldParkAfterFailedAcquire()方法,对节点的waitStatus状态进行设置值
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 判断pred节点状态是否为-1
if (ws == Node.SIGNAL)
return true;
// 如果pred节点状态大于0,则一直找直至找到上一节点的waitStatus <= 0的,并将此节点的下节点设置为node
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 执行CAS操作将pred节点设置为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
其中的waitStatus可以在这里查看AQS原理解析
parkAndCheckInterrupt()方法将当前线程设置为阻塞状态,并判断当前线程是否中断
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
释放许可
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 是否需要唤醒节点
if (ws == Node.SIGNAL) {
// 恢复节点初始值0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒.next节点
unparkSuccessor(h);
}
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 只有当head为null,或者head == tail的时候,才会跳出循环。即阻塞队列中没有阻塞节点
if (h == head) // loop if head changed
break;
}
}
其中Semaphore是在初始化时设置State为2,在获取到许可证时state减1,释放许可证时加1,最终保证state为初始化时的值。