Semaphore信号量底层依赖于AQS实现的信号量同步工具,可以用来做限流操作;我们可以通过Semaphore来定义信号量的资源池,通过acquire方法来获取资源,使用完成资源后,通过调用release方法释放资源池资源
使用实例
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 5; i++) {
new Thread(new Task(semaphore,"jiy"+i)).start();
}
}
static class Task extends Thread{
Semaphore semaphore;
public Task(Semaphore semaphore,String name){
this.semaphore =semaphore;
this.setName(name);
}
public void run(){
try {
semaphore.acquire();
System.out.println("获取线程资源了"+Thread.currentThread().getName());
Thread.sleep(3000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定义一个Task实现Thread类,在run方法里面打印线程名称,并且获取资源锁,睡眠3秒之后释放锁;
执行的结果会看到 每次都只会打印出两个线程名称,其他的线程会被阻塞,3秒之后又会打印出两个线程名称。。
底层实现 Semaphore源码
abstract static class Sync extends AbstractQueuedSynchronizer {
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
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;
}
}
}
Semaphore构造方法
Semaphore内部持有Sync实现自AQS,内部自己定义了公平和非公平的类;
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
通过Semaphore构造方法,可以看出,Semaphore默认是使用非公平锁;
//NonfairSync
NonfairSync(int permits) {
super(permits);
}
Sync(int permits) {
setState(permits);
}
setState(permits);设置初始化的资源数大小;
Semaphore的acquire方法
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//AQS
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//尝试进行加锁
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
首先我们可以看到调用acquire方法会调用sync.acquireSharedInterruptibly(1)方法;通过这个方法底层调用的AQS的acquireSharedInterruptibly()
在AQS的acquireSharedInterruptibly方法中,先判断当前线程是否中断;尝试调用tryAcquireShared(arg)进行尝试加锁;tryAcquireShared(arg)是一个钩子方法,
会调用Semaphore类里面的tryAcquireShared方法;
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
//获取当前的信号量数量
int available = getState();
//获取可用的信号量数量
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
开始尝试加锁操作,可以从nonfairTryAcquireShared方法中看到;我们从第一个线程加锁来看
-
获取当前信号量数量 (设置的初始值2)
-
获取可用的信号量数量(2-1 = 1)
-
判断可用信号量是否小于0;(由于是第一次加锁,所以可用信号量为1 是大于0,则需要执行第四步)
-
使用CAS的方式来修改state数量(将state从2修改为1)
-
返回可用信号量数(1)
整个使用tryAcquireShared的流程是这样的;这种是可以加锁成功的场景,由于信号量是存在的;那么接下来我们来看一下当信号量已经使用完了情况;
-
获取当前信号量数量 (0)
-
获取可用的信号量数量(0-1 = -1)
-
判断可用信号量是否小于0;(-1)
-
返回可用信号量数(-1)
//AQS
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//尝试进行加锁
if (tryAcquireShared(arg) < 0)
//由于tryAcquireShared()方法返回的值为-1,所以需要执行下面方法
doAcquireSharedInterruptibly(arg);
}
通过第二个场景,我们可以看到由于tryAcquireShared()返回值为-1,所以会执行doAcquireSharedInterruptibly(arg);
//AQS
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 1 把获取锁失败的线程添加到等待队列中
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;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
首先我们看到的是1 final Node node = addWaiter(Node.SHARED); 把获取锁失败的线程添加到等待队列
private Node addWaiter(Node mode) {
//把当前线程封装成一个node
Node node = new Node(Thread.currentThread(), mode);
// 记录tail节点
Node pred = tail;
//如果tail节点不等于null
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
首先在整个阻塞队列里面是空 head和tail都为空;
1 Node pred = tail; 记录tail节点 if (pred != null) 判断tail节点是否为空;(由于是等待队列里面没有任何数据,tail节点为空)
2 直接执行enq(node);方法;自旋(死循环),Node t = tail;获取tail节点;if (t == null) 判断tail节点是否为空(初始值的时候为空);
所以进入方法执行 if (compareAndSetHead(new Node())) 通过CAS的方式设置一个head节点为一个空的节点(new Node()); tail = head;然后将head和tail都指向这个空节点;(此时head和tail都不为null了)
3 继续执行 for (;;)自旋操作,if (t == null) 再一次判断tail节点是否为空,这一次tail节点不为空了,执行else操作
4 node.prev = t;将传入的节点的前置节点指向tail节点,compareAndSetTail(t, node)然后通过CAS的方式将当前节点Node设置为tail节点, t.next = node;然后将t.next指向当前节点(注意,此时的t不是tail节点,而是head节点,tail节点已经通过CAS设置成为了当前的node节点了);接下来返回t(head节点);
上述看完了final Node node = addWaiter(Node.SHARED);把获取锁失败的线程加入到阻塞队列中之后,继续看下面的操作;
//AQS
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
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;
}
}
//shouldParkAfterFailedAcquire设置前置节点的waitStatus=-1,并且清除waitStatus>0的无用数据
//parkAndCheckInterrupt 将当前线程进行挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
1 首先进行一个自旋 for (;;) ;final Node p = node.predecessor()获取当前node节点的前置节点;if (p == head)判断当前节点是否为头节点(根据我们上图看到的Node的前置节点是头节点,所以执行第二步);
2 int r = tryAcquireShared(arg); 尝试再次获取锁;正常情况下这一步是会失败的;
3 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) 执行这个判断方法;
3.1 shouldParkAfterFailedAcquire(p, node)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前置节点的等待状态
int ws = pred.waitStatus;
//如果前置节点的等待状态为-1,则直接返回true,
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 如果前置节点是初始值,或者是共享值,将前置节点设置为-1,阻塞后续的节点
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
3.1.1 首先获取头节点等待状态,由于是new Node()创建的头节点,所以ws = 0的;compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 执行这一步将头节点的waitStatus从0修改为-1;并且返回false;
3.1.2 然后继续触发步骤1的自旋操作,然后来到了3.1的方法;这一次ws = -1的,所以会返回true;然后执行3.2的方法;
3.2 parkAndCheckInterrupt() 将当前的线程通过 LockSupport.park(this);进行挂起,然后返回当前线程的中断状态(false,当前线程只是阻塞并没有中断)
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
所以整个线程就阻塞在了doAcquireSharedInterruptibly方法上自旋里面等待被唤醒;
Semaphore的release方法
public void release() {
sync.releaseShared(1);
}
//AQS
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
首先调用semaphore的release方法,会调用AQS的releaseShared方法;
通过钩子方法tryReleaseShared(arg)来尝试解锁操作
protected final boolean tryReleaseShared(int releases) {
//自旋
for (;;) {
//获取当前的信号量数量
int current = getState();
// 当前信号量数量 + 1 (0 + 1)
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
//通过CAS的方式修改state值
if (compareAndSetState(current, next))
return true;
}
}
在tryReleaseShared方法里面进行自旋操作,首先获取到当前信号量,然后加上要释放的信号量的数量,然后通过CAS的方式进行修改state(使用自旋是为了避免在并发场景下CAS操作的失败);然后返回true,继续执行tryReleaseShared()方法,唤醒后置节点线程
private void doReleaseShared() {
//自旋
for (;;) {
//获取头节点
Node h = head;
//判断头节点不为空,并且头节点和尾节点不相等(中间还有其他的节点)
if (h != null && h != tail) {
//获取头节点的等待状态
int ws = h.waitStatus;
//判断头节点的等待状态是否为-1
if (ws == Node.SIGNAL) {
//如果头节点的等待状态为-1,则将它修改为0
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;
}
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//如果当前头节点等待状态小于0,则将状态设置为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//获取后置节点
Node s = node.next;
//如果后置节点为空;或者后置节点等待状态大于0
if (s == null || s.waitStatus > 0) {
s = null;
//获取尾节点,从后往前遍历,找到等待状态小于0的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//如果后置节点不为Null
if (s != null)
//唤醒后置节点
LockSupport.unpark(s.thread);
}
doReleaseShared()方法主要是判断是否还有其他的等待节点,然后将头节点的waitStatus修改为0
unparkSuccessor(h)方法是,获取头节点的后置节点,如果后置节点不为空或者waitStatus不大于0,则唤醒后置节点;如果不满足,则从尾节点进行向前循环,找到第一个满足waitStatus<=0的节点进行唤醒;