使用demo
Semaphore,的用法,demo;_三井08的博客-CSDN博客_semaphoredemo
原理简介
以下链标识的是AQS中的node链表
head->node1->node2->node3->tail(node4)
1、初始化时对state进行赋值。比如说2,标识着允许的信号量
2、当线程1调用acquire方法时,当前需要一个信号量,判断2大于等于1,则设置state为1,设置当前节点为头结点,并尝试去唤醒下一个node(因为node2没有添加,所以不会继续唤醒),并且允许当前线程运行
此时的链表结构是head(node1)->node2->node3->tail(node4)
3、当线程2调用acquire方法时,当前需要一个信号量,判断1大于等于1,则设置state为0,设置当前节点为头结点,并尝试去唤醒下一个node(因为node2没有添加,所以不会继续唤醒),并且允许当前线程运行
此时的链表结构是head(node2)->node3->tail(node4)
4、当线程3调用acquire方法时,当前需要一个信号量,判断0小于1,则设置state为1,则标识当前节点竞争失败,并且park住。
此时的链表结构是head(node2)->node3->tail(node4)
5、当线程4调用acquire方法时,当前需要一个信号量,判断0小于1,则设置state为1,则标识当前节点竞争失败,并且park住。
此时的链表结构是head(node2)->node3->tail(node4)
6、当线程1调用release方法时,此时会设置state为1,同时会从头结点的下一个节点进行唤醒,发现线程3需要1个信号量,此时则设置state为1,并且unpark当前线程。然后把当前线程变成变为头结点,尝试唤醒线程4,发现信号量不足,则线程4继续park
此时的链表结构是head(node3)->tail(node4)
方法分析
acquire
本质是调用AQS的acquire方法
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
release
释放许可证,并且会调用releaseShared方法。(会从头部节点开始按顺序唤醒后续的共享节点)
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
构造方法 Semaphore
设置允许的信号量,默认使用非公平竞争
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
Semaphore(int permits, boolean fair)
可以设置是公平还是非公平
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
内部类Sync
构造方法 Sync
其主要是通过控制AQS的state来标识有几个状态可以使用
Sync(int permits) {
setState(permits);
}
nonfairTryAcquireShared
获取能设置的信号量,当剩余可以获取的信号量大于需要获取的信号量时,通过cas的方式修改剩下可以获取的信号量。
如果剩余的信号量比要获取的数量多,并且没有设置成功(被别的线程抢占设置),则进入自旋
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
tryReleaseShared
尝试释放信号量。通过cas的方法,让state的值加上当前释放的值
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;
}
}
reducePermits
把允许的信号量值减少。即是设置state的值为当前state的值减去要减小的值
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
drainPermits
把允许获取的信号量值置为0
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
内部类NonfairSync(继承自Sync)
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
内部类FairSync(继承自Sync)
FairSync(int permits) {
super(permits);
}
tryAcquireShared
在尝试去修改state值的时候判断Node的双向链表中是否有正在等待的节点。其公平非公平原理和ReentrantLock基本一致
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;
}
}
AQS内部共享方法
acquireShared
调用子Sync实现的tryAcquire方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
doAcquireShared
1、把当前节点添加到队列中
2、循环获取头结点
3、判断头结点是否可以能获取到足够的凭证,如果可以,则设置当前节点为头结点,并且唤醒下一个节点
4、如果不行,则park住当前节点
private void doAcquireShared(int arg) {
在addWaiter中以Node.SHARE构建一个node节点,添加到node队列的结尾并返回构建的节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
获取node节点的前驱节点
final Node p = node.predecessor();
如果node节点的前驱节点是头结点,注意这里和独占式的区别,独占式在这里CAS设置状态
if (p == head) {
获取节点的状态
int r = tryAcquireShared(arg);
if (r >= 0) {
将node节点设置为头结点 ,如果r大于0,原来的头结点的状态小于0,就获取node节点的后继节点,如果后继节点为null或者后继节点是共享节点,就激活node节点的后继节点
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);
}
}
releaseShared
释放共享节点
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
doReleaseShared
释放头结点,头结点在被释放之后,会经过原先的acquire中的park之后的循环。唤起后续的节点。
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;
}
//更新状态成功就激活头结点的有效后继节点
unparkSuccessor(h);
}else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) //如果头结点的初始状态为0,就CAS将状态更新为-3,如果成功,就判断头结点是否被修改,
continue; //CAS 失败就一直循环
}
if (h == head) //如果头结点指针没有变化,就一直循环,否则,退出循环
break;
}
}
setHeadAndPropagate
把当前节点设置为头结点,并且在判断为共享节点的情况下,往下释放节点
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; //记录下原来的头结点
setHead(node);//将node节点设置为头结点
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared()) //如果是共享节点就激活头结点的后继节点
doReleaseShared();
}
}
CountDownLatch demo
初始化构造传入参数3,即设置aqs中的state状态为3
countDown方法其主要是设置其state值减一,并且判断其state值减到0的时候会唤醒在等待的线程。
await方法主要是判断当前state值是否为0,如果为0则继续执行,如果不为0则等待
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
// 只能使用一次
CountDownLatch countDownLatch = new CountDownLatch(3);
countDownLatch.countDown();
countDownLatch.await();
}
CyclicBarrier demo
循环栅栏是相当于有构造方法个数的await之后会统一放行
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(2);
new Thread(() -> {
System.out.println("线程1开始");
try {
cb.await();
System.out.println("线程1结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(() -> {
System.out.println("线程2已开始");
try {
cb.await();
System.out.println("线程2结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(() -> {
try {
Thread.sleep(2000L);
System.out.println("线程3已开始");
cb.await();
System.out.println("线程3结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(() -> {
System.out.println("线程4已开始");
try {
cb.await();
System.out.println("线程4结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
}