1.Semaphore 是什么
Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。
可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。
2.Semaphore常用方法
Semaphore 最中要的两个方法是Semaphore#acquire 和Semaphore#release方法
- acquire:当前线程会尝试去同步队列获取一个令牌,当获取成功时得到令牌,获取失败的时候就挂起等待
- release:当前线程尝试释放一个令牌,释放令牌成功之后,同时会唤醒同步队列中的另一个线程,让其尝试获取令牌,如果它获取令牌成功则使其继续运行
//参考JDK 11
public class Semaphore implements java.io.Serializable {
private final Sync sync;
//获取一个许可证令牌
//在获取到许可证令牌、或者被其他线程调用中断之前线程一直处于阻塞状态
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//获取指定数量许可证令牌
//在获取到许可证令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
//获取一个许可证令牌,在获取到令牌之前线程一直处于阻塞状态
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
//获取指定数量令牌,在获取到令牌之前线程一直处于阻塞状态
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
//尝试获得令牌,返回获取令牌成功或失败,不阻塞线程
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
//获取给定数量的许可证(如果可用),
//如果可用的许可证令牌足够, 立即返回值true,将可用许可证数量减少给定数量。
//如果可用的许可证令牌不足,则此方法将立即返回值false,并且可用许可证的数量不变。
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
//尝试获得许可证令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//释放一个许可证令牌,唤醒一个获取许可证令牌不成功的阻塞线程
public void release() {
sync.releaseShared(1);
}
//释放给定数量的许可证令牌
//如果有任何线程试图获取许可证,则选择一个线程并提供刚刚发布的许可证。
//如果可用许可证的数量满足该线程的请求,则该线程将(重新)启用以进行线程调度;
//否则,线程将等待足够的许可证可用。
//如果在满足该线程的请求后仍然有许可证可用
//那么这些许可证将依次分配给试图获取许可证的其他线程
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
//返回可用的许可证令牌数量
public int availablePermits() {
return sync.getPermits();
}
//清空令牌把可用许可证令牌数置为0,返回清空令牌的数量
public int drainPermits() {
return sync.drainPermits();
}
//查询信号量公平设置true 公平 false非公平
public boolean isFair() {
return sync instanceof FairSync;
}
//返回查询是否有线程正在等待获取
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
//获取等待队列里阻塞的线程数
public final int getQueueLength() {
return sync.getQueueLength();
}
//返回包含可能正在等待获取的线程的集合
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
}
3.Semaphore 使用场景
通常用于那些资源有明确访问数量限制的场景,常用于限流 。比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。
举个例子, 停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。例如下面的测试代码
- 创建令牌数为2的信号量,表示车库只有2个车位
- 循环创建10个线程尝试获取信号量的令牌,模拟表示车获取空闲车位
- 线程获取信号量后5秒释放,表示每辆车最多停5秒
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) throws InterruptedException {
//测试信号量为2
Semaphore semaphore=new Semaphore(2);
for (int i = 0; i < 10; i++) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" in");
int time=5000;
Thread.sleep(time);
System.out.println(Thread.currentThread().getName()+":semaphore#release ,after "+time/1000+"s");
semaphore.release();;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread No."+i);
thread.start();
}
}
}
测试结果:因为信号量令牌数量为2,所以开始时只有2个线程acquire获取到令牌,打印 in ,当两个线程release 释放令牌后,其他线程中的两个线程才能获取到令牌,再次in进入
Thread No.0 in
Thread No.3 in
Thread No.0:semaphore#release ,after 5s
Thread No.3:semaphore#release ,after 5s
Thread No.7 in
Thread No.5 in
Thread No.7:semaphore#release ,after 5s
Thread No.5:semaphore#release ,after 5s
Thread No.4 in
Thread No.1 in
Thread No.1:semaphore#release ,after 5s
Thread No.4:semaphore#release ,after 5s
Thread No.6 in
Thread No.8 in
Thread No.6:semaphore#release ,after 5s
Thread No.8:semaphore#release ,after 5s
Thread No.2 in
Thread No.9 in
Thread No.2:semaphore#release ,after 5s
Thread No.9:semaphore#release ,after 5s