Semaphore
Semaphore概述
板书栏:
Semaphore是信号量,指定一定数量的资源,一个线程可以获取一个或者多个资源,当资源为0时,其他线程挂起,直到有线程归还资源,才会唤醒挂起的线程。
Semaphore是基于AQS实现的。资源数量是通过AQS中的state属性记录,每有一个线程来申请资源,state就减相应的数量,有线程归还资源,state就加上相应的数量。state值为0时,来申请资源的线程被挂起。有线程归还资源时,才会唤醒挂起的线程。
要点栏:
要点1:
Semaphore用来进行资源管理,有空闲资源时,会将资源给申请的线程,没有空闲资源时,挂起申请的线程。
Semaphore应用
板书栏:
public class Test {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(10);
new Thread(() -> {
System.out.println("一家三口要进来");
try {
semaphore.acquire(3);
System.out.println("一家三口进来了");
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(3);
System.out.println("一家三口离开了");
}
}).start();
for (int i=0; i<7; i++) {
int j = i;
new Thread(()->{
System.out.println(j + "要进来");
try {
semaphore.acquire();
System.out.println(j + "进来了");
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
System.out.println(j + "离开了");
}
}).start();
}
Thread.sleep(10);
System.out.println("main要进来");
if (semaphore.tryAcquire()) {
System.out.println("main进来了");
semaphore.release();
System.out.println("main离开了");
} else {
System.out.println("资源不够,main进不来");
}
Thread.sleep(7000);
if (semaphore.tryAcquire()) {
System.out.println("main进来了");
} else {
System.out.println("资源不够,main进不来");
}
semaphore.release();
System.out.println("main离开了");
}
}
要点栏:
要点1:
获取资源的方法:
- acquire():获取一个资源;响应中断,抛出异常
- acquire(int):获取指定个数资源;响应中断,抛出异常
- tryAcquire():获取一个资源,立即返回
- tryAcquire(time, unit):获取一个资源,等待一定时间;响应中断,抛出异常
- tryAcquire(int):获取指定个数资源,立即返回
- tryAcquire(int,time,unit):获取指定个数资源,等待一定时间;响应中断 ,抛出异常
- acquireUninterruptibly():获取一个资源,不响应中断
- acquireUninterruptibly(int):获取指定个数资源,不响应中断
释放资源的方法:
- release():释放一个资源
- release(int):释放指定个数资源
要点2:
获取资源后,一定要释放响应数量的资源,否则最后资源会被耗尽。
Semaphore源码
Semaphore基本属性
板书栏:
public class Semaphore implements java.io.Serializable {
private static final long serialVersionUID = -3222578661600680210L;
private final Sync sync;
// 抽象内部类
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) {
// 将资源个数赋值给state
setState(permits);
}
final int getPermits() {
return getState();
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
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;
}
}
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;
}
}
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
// 非公平
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;
}
}
}
}
要点栏:
要点1:
Semaphore有三个内部类,其中Sync是抽象内部类,NonfairSync和FairSync继承了Sync
要点2:
Sync将资源数量通过setState方法赋值给了AQS中的state属性
要点3:
NonfairSync和FairSync的核心区别在于tryAcquireShared方法实现不同
Semaphore基本方法
构造方法
板书栏:
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
要点栏:
要点1:
无参构造方法默认使用的是非公平方法
要点2:
有参构造方法可以指定使用非公平还是公平方法
acquire无参方法
背景栏:
非公平的实现
板书栏:
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
// 响应中断,抛出中断异常
throw new InterruptedException();
// tryAcquireShared返回值小于0,说明没有资源了,当前线程要被挂起
// tryAcquireShared返回值大于等于0,说明当前线程获取到资源,正常执行业务代码
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 获取state值
int available = getState();
// 获取剩余的资源
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
// 返回剩余资源
return remaining;
}
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 将当前线程封装成Node,添加到双向链表中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 获取前一个节点
final Node p = node.predecessor();
if (p == head) {
// 前一个节点是head节点,再次尝试获取资源
int r = tryAcquireShared(arg);
if (r >= 0) {
// 获取资源成功
// 当前节点设置为head节点
// 唤醒后面的节点线程
setHeadAndPropagate(node, r);
p.next = null;
failed = false;
return;
}
}
// 获取资源失败
// 挂起当前线程,并且将前一个节点状态改成-1
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// node:当前线程封装的节点
// propagate:当前线程拿到资源后,剩余的资源数量
private void setHeadAndPropagate(Node node, int propagate) {
// h赋值为head节点
Node h = head;
// 将当前线程节点设置为head节点
setHead(node);
// 下面的if判断中有很多条件满足后,都去尝试唤醒线程获取资源
// 这样做是为了消除jdk1.5的BUG:有资源,但是存在线程没有被唤醒
// 所以jdk1.8中这里就很轻易去唤醒线程去尝试获取一下资源
if (
// 判断剩余的资源数量是不是大于0
// 如果大于0,唤醒当前线程节点后面等待的线程
propagate > 0 ||
// 判断之前head节点是否已经被gc释放
// 如果被释放,不知道现在有没有新的线程释放资源
// 唤醒当前线程节点后面等待的线程尝试获取资源
h == null ||
// 之前的head节点还没有被释放
// 判断head节点的状态是否小于0
// 小于0说明双向链表中还有其他线程挂起,唤醒线程尝试获取资源
h.waitStatus < 0 ||
// 判断当前线程作为head节点的状态是否小于0
// 小于0说明双向链表中还有其他线程挂起,唤醒线程尝试获取资源
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
// 唤醒后面的节点
doReleaseShared();
}
}
private void doReleaseShared() {
for (;;) {
// h赋值为head节点
Node h = head;
// 判断head节点不为null,并且双向链表中有节点
if (h != null && h != tail) {
// ws赋值为head节点的状态
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
// head节点后面有节点
// 将head节点状态设置为0,防止重复唤醒
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒后面的节点
unparkSuccessor(h);
}
// head节点的状态为0,将其改成-3,表示后面有节点可以唤醒
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// head节点没有变化,没有并发情况发生
if (h == head)
break;
}
}
要点栏:
要点1:
acquire方法是响应中断的,所以方法一开始就对线程是否中断做了判断
要点2:
如果state属性值大于等于当前线程申请的资源数,那么线程正常执行业务代码;否则线程会被挂起
要点3:
不带有时间参数的tryAcquire方法中,只使用了非公平的实现,因为只有非公平的情况下,才有可能在有线程排队的时候获取资源。公平情况下没有意义。
要点4:
带有时间参数的tryAcquire方法是执行AQS提供的acquire方法。因为这个方法排队的情况下,即使是公平的情况下也有可能获取到资源。
要点5:
acquireUninterruptibly的无参和有参方法是在挂起线程后,不会因为线程的中断而抛出异常,而是继续尝试获取资源,资源不够就挂起。
要点6:
公平情况下,acquire方法中只有一个地方与非公平情况下实现不同,就是tryAcquireShared方法
protected int tryAcquireShared(int acquires) {
for (;;) {
// 如果双向链表中没有节点,不走下面的if语句
// 如果当前线程节点是head节点的next节点,也不走下面的if语句
if (hasQueuedPredecessors())
return -1;
// 尝试获取资源
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
公平情况下,当前线程在获取资源前,需要对双向链表中的等待节点的情况做判断。如果有线程在等待,当前线程需要去排队,否则尝试获取资源。
release方法
板书栏:
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 释放资源成功,执行doReleaseShared方法
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 获取资源数
int current = getState();
// 资源数加上释放的资源数
int next = current + releases;
// 对资源数做健壮性判断
if (next < current)
throw new Error("Maximum permit count exceeded");
// 通过CAS将新的资源数赋值给state
if (compareAndSetState(current, next))
return true;
}
}
private void doReleaseShared() {
for (;;) {
// h赋值为head节点
Node h = head;
// 判断head节点不为null,并且双向链表中有节点
if (h != null && h != tail) {
// ws赋值为head节点的状态
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
// head节点后面有节点
// 将head节点状态设置为0,防止重复唤醒
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒后面的节点
unparkSuccessor(h);
}
// head节点的状态为0,将其改成-3,表示后面有节点可以唤醒
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// head节点没有变化,没有并发情况发生
if (h == head)
break;
}
}
要点栏:
要点1:
release方法只有一种实现,不分公平还是非公平
要点2:
release方法释放资源就是将state值加上释放的资源数量