初识Semaphore:
Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,一般可用于流量的控制。
Semaphore用于限制可以访问某些资源(物理或逻辑的)的线程数目,他维护了一个许可证集合,有多少资源需要限制就维护多少许可证集合,假如这里有N个资源,那就对应于N个许可证,同一时刻也只能有N个线程访问。一个线程获取许可证就调用acquire方法,用完了释放资源就调用release方法。
Semaphore管理一系列许可。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可这个对象,Semaphore只是维持了一个可获得许可证的数量。
Semaphore 只有3个操作:
构造方法一:Semaphore(int permits) 创建具有给定的许可数和非公平的公平设置的 Semaphore
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
构造方法二:Semaphore(int permits, boolean fair) 创建具有给定的许可数和给定的公平设置的 Semaphore
/**
* @param fair :是否公平,默认非公平;
* false:非公平;true:公平
*/
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
acquire() 获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquire(int permits) 获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
acquireUninterruptibly() 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
tryAcquire()尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
tryAcquire(long timeout, TimeUnit unit)尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
release() 释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
public void release() {
sync.releaseShared(1);
}
release(int permits)释放给定数目的许可,将其返回到信号量
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
hasQueuedThreads() 等待队列里是否还存在等待线程。
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
getQueueLength()获取等待队列里阻塞的线程数。
public final int getQueueLength() {
return sync.getQueueLength();
}
drainPermits()清空令牌把可用令牌数置为0,返回清空令牌的数量。
public int drainPermits() {
return sync.drainPermits();
}
availablePermits()返回可用的令牌数量
public int availablePermits() {
return sync.getPermits();
}
代码示例:
场景分析:相信有车一族最头疼的就是停车了吧,每次到一个景点去停车,都会被保安告诉车位已满,需要等待,并且只能有车离开才能让下一辆车进去;有的车库在门前会显示有几个车位,剩余多少车位。那么这个功能通过代码是如何实现的呢?简易版代码如下: ```java public class SemaphoreTest {private static int count = 20; //停车场同时容纳的车辆8, 此处默认使用非公平的锁,你们可以私下去使用公平锁去试试,就会发现,先到先得 // 写法示例: Semaphore semaphore = new Semaphore(8, true); private static Semaphore semaphore = new Semaphore(8);
private static ExecutorService service = Executors.newFixedThreadPool(count);
public static void main(String[] args) { test(); }
public static void test() {
//模拟100辆车进入停车场
for (int i = 0; i < 20; i++) {
int num = i + 1;
service.execute(() -> {
try {
System.out.println("车牌 00000" + num + "来到停车场");
if (semaphore.availablePermits() == 0) {
System.out.println("车位不足,请耐心等待");
}
semaphore.acquire();
System.out.println("车牌 00000" + num + "成功进入停车场");
Thread.sleep(new Random().nextInt(10000));
System.out.println("车牌 00000" + num + "驶出停车场");
semaphore.release();//释放令牌,腾出停车场车位
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
service.shutdown();
} }
执行结果:
> 车牌 000001来到停车场
> 车牌 000003来到停车场
> 车牌 000002来到停车场
> 车牌 000003成功进入停车场
> 车牌 000001成功进入停车场
> 车牌 000005来到停车场
> 车牌 000005成功进入停车场
> 车牌 000006来到停车场
> 车牌 000006成功进入停车场
> 车牌 000004来到停车场
> 车牌 000002成功进入停车场
> 车牌 000004成功进入停车场
> 车牌 000007来到停车场
> 车牌 000007成功进入停车场
> 车牌 000008来到停车场
> 车牌 000008成功进入停车场
> ==================================
> 车牌 000009来到停车场
> 车位不足,请耐心等待
> 车牌 0000010来到停车场
> 车位不足,请耐心等待
> 车牌 0000011来到停车场
> 车位不足,请耐心等待
> 车牌 0000012来到停车场
> 车位不足,请耐心等待
> 车牌 0000013来到停车场
> 车位不足,请耐心等待
> 车牌 0000014来到停车场
> 车位不足,请耐心等待
> 车牌 0000015来到停车场
> 车位不足,请耐心等待
> 车牌 0000016来到停车场
> 车位不足,请耐心等待
> 车牌 0000017来到停车场
> 车位不足,请耐心等待
> 车牌 0000018来到停车场
> 车位不足,请耐心等待
> 车牌 0000019来到停车场
> 车位不足,请耐心等待
> 车牌 0000020来到停车场
> 车位不足,请耐心等待
> ===================================
> 车牌 000004驶出停车场
> 车牌 000009成功进入停车场
> 车牌 000002驶出停车场
> 车牌 0000010成功进入停车场
> 车牌 0000010驶出停车场
> 车牌 0000011成功进入停车场
> 车牌 0000011驶出停车场
> 车牌 0000012成功进入停车场
> 车牌 000003驶出停车场
> 车牌 0000013成功进入停车场
> 车牌 000006驶出停车场
> 车牌 0000014成功进入停车场
> 车牌 000008驶出停车场
> 车牌 0000015成功进入停车场
> 车牌 000007驶出停车场
> 车牌 0000016成功进入停车场
> 车牌 0000015驶出停车场
> 车牌 0000017成功进入停车场
> 车牌 000005驶出停车场
> 车牌 0000018成功进入停车场
> 车牌 000009驶出停车场
> 车牌 0000019成功进入停车场
> 车牌 0000019驶出停车场
> 车牌 0000020成功进入停车场
> 车牌 000001驶出停车场
> 车牌 0000012驶出停车场
> 车牌 0000017驶出停车场
> 车牌 0000016驶出停车场
> 车牌 0000018驶出停车场
> 车牌 0000013驶出停车场
> 车牌 0000014驶出停车场
> 车牌 0000020驶出停车场
<a name="sm4ko"></a>
## 源码解析:(默认使用不公平许可证)
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1692011/1611893547674-6d0e200e-ebe1-4c42-98a1-ce94d11d73be.png#align=left&display=inline&height=659&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1318&originWidth=1490&size=1773166&status=done&style=none&width=745)<br />通过上面案例,我们可以发现,首先通过 **semaphore.availablePermits() **当前资源是否充足,不足则告诉其他车主,停车场满了别进来了。然后调用 **semaphore.acquire() **去获取令牌,源码如下:
```java
/**
* 获取1个令牌
*/
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 共享模式下获取令牌,获取成功则返回,失败则加入阻塞队列,挂起线程
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取令牌,arg为获取令牌个数,当可用令牌数减当前令牌数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
/**
* 1、创建节点,加入阻塞队列,
* 2、重双向链表的head,tail节点关系,清空无效节点
* 3、挂起当前节点线程
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//创建节点加入阻塞队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获得当前节点pre节点
final Node p = node.predecessor();
if (p == head) {
//返回锁的state
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);
}
}
获取到了车位,然后开始停车。。。。。。下班了,开车回家,此时我们是不是要离开车库,即去释放车位;
源码如下:
/**
* 释放令牌
*/
public void release() {
sync.releaseShared(1);
}
/**
* 释放共享锁,同时唤醒所有阻塞队列共享节点线程
*/
public final boolean releaseShared(int arg) {
//释放共享锁
if (tryReleaseShared(arg)) {
//唤醒所有共享节点线程
doReleaseShared();
return true;
}
return false;
}
/**
* 共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回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;
}
}
/**
* 唤醒所有共享节点线程
*/
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {//是否需要唤醒后继节点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改状态为初始0
continue;
unparkSuccessor(h);//唤醒h.nex节点线程
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE));
}
if (h == head) // loop if head changed
break;
}
}