Java并发编程三大神器之Semaphore
- 1、Semaphore是什么
- 2、Semaphore小试牛刀
- 3、Semaphore和CountDownLatch组合使用
- 4、Semaphore常用方法
- 5、Semaphore 结语
1、Semaphore是什么
Semaphore 是一个计数信号量,是JDK1.5引入的一个并发工具类,位于java.util.concurrent包中。可以控制同时访问资源的线程个数。Semaphore机制是提供给线程抢占式获取许可,所以他可以实现公平或者非公平,类似于ReentrantLock。
《Java并发编程艺术》中说:Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
举个栗子:我们去停车场停车,停车场总共只有5个车位,但是现在有8辆汽车来停车,剩下的3辆汽车要么等其他汽车开走后进行停车,要么去其他停车场找别的停车位。
举个栗子:公园里面的厕所只有4个坑,假如有10个人要上厕所的话,那么同时能去上厕所的人就只能有4个,还剩下6个人只能在外面等待。当4个人中有任何一个人离开后,其中在等待的人中才有一个人可以继续使用,依次下去,直到所有人都上完厕所。
2、Semaphore小试牛刀
Semphore计数信号量由 new Semaphore(N) 指定数量N的 “许可” 初始化。每调用一次 acquire(),一个许可会被调用线程取走。每调用一次 release(),一个许可会被返还给信号量。因此,在没有任何 release() 调用时,最多有 N 个线程能够通过 acquire() 方法,N是该信号量初始化时的许可的指定数量。这些许可只是一个简单的计数器。
如下的例子,有一个商场拥有4个车位的停车场,有10辆车需要停车,当停进4辆车后,其余的6辆车就需要等待已经停进去的车开出去,才可以再停进去。
public class ParkingLot extends Thread {
// 信号量
private Semaphore semaphore;
// 同时允许多少个线程同时执行
private int num;
public ParkingLot(Semaphore semaphore, int num) {
this.semaphore = semaphore;
this.num = num;
}
@Override
public void run() {
try {
//汽车驶入停车场,需要获取一个许可
semaphore.acquire();
System.out.println(LocalDateTime.now() + " 第" + this.num + "辆汽车驶入停车场...");
Thread.sleep(2000);
System.out.println(LocalDateTime.now() + " 第" + this.num + "辆汽车驶出停车场>>>");//汽车驶出停车场,会释放一个许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ParkingLotSemaphoreDemo {
// 停车场总共有5个车位
private static final int MAX_AVAILABLE = 4;
public static void main(String[] args) {
//只有5个车位,就是说同时只能允许5辆汽车进行停车
Semaphore semaphore = new Semaphore(MAX_AVAILABLE);
//模拟10辆车驶入停车场
for (int i = 1; i <= 10; i++) {
new ParkingLot(semaphore, i).start();
}
}
}
3、Semaphore和CountDownLatch组合使用
比如说
public class SemaphoreTest {
public static void main(String[] args) throws InterruptedException {
// 初始化五个车位
Semaphore semaphore = new Semaphore(5);
// 等所有车子
final CountDownLatch latch = new CountDownLatch(8);
for (int i = 1; i <= 8; i++) {
int finalI = i;
if (i == 5) {
Thread.sleep(1000);
new Thread(() -> {
stopCarNotWait(semaphore, finalI);
latch.countDown();
}).start();
continue;
}
new Thread(() -> {
stopCarWait(semaphore, finalI);
latch.countDown();
}).start();
}
latch.await();
log("总共还剩:" + semaphore.availablePermits() + "个车位");
}
// 停车 等待车位
private static void stopCarWait(Semaphore semaphore, int finalI) {
String format = String.format("车牌号%d", finalI);
try {
semaphore.acquire(1);
log(format + "找到车位了,去停车了");
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
} finally {
semaphore.release(1);
log(format + "开走了");
}
}
// 不等待车位 去其他地方停车
private static void stopCarNotWait(Semaphore semaphore, int finalI) {
String format = String.format("车牌号%d", finalI);
try {
if (semaphore.tryAcquire()) {
log(format + "找到车位了,去停车了");
Thread.sleep(10000);
log(format + "开走了");
semaphore.release();
} else {
log(format + "没有停车位了,不在这里等了去其他地方停车去了");
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 日志
public static void log(String content) {
// 格式化
DateTimeFormatter fmTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now.format(fmTime) + " " + content);
}
}
从输出结果可以看到车牌号6这辆车一看没有车位了,就不在这个地方傻傻的等了,而是去其他地方了,但是车牌号7和车牌号8分别需要等到车库开出2辆车空出2个车位后才停进去。这就体现了 Semaphore
的 acquire() 方法如果没有获取到凭证它就会阻塞,而 tryAcquire() 方法如果没有获取到凭证不会阻塞的。
4、Semaphore常用方法
public Semaphore(int permits):创建一个信号量,参数permits表示许可数目,即同时可以允许多少线程进行访问。默认采用的是非公平的策略。
public Semaphore(int permits, boolean fair):创建一个信号量,参数permits表示许可数目,即同时可以允许多少线程进行访问。多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可。
public void acquire():用于获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。【会阻塞】
public void acquire(int permits):用于获取permits个许可,若无许可能够获得,则会一直等待,直到获得许可。【会阻塞】
public void release():用于释放一个许可。在释放许可之前,得先获得许可。【会阻塞】
public void release(int permits):用于释放permits个许可。在释放许可之前,得先获得许可。【会阻塞】
public boolean tryAcquire():尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false。【不会阻塞】
public boolean tryAcquire(long timeout, TimeUnit unit):尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false。【不会阻塞】
public boolean tryAcquire(int permits):尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false。【不会阻塞】
public boolean tryAcquire(int permits, long timeout, TimeUnit unit):尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false。【不会阻塞】
public int availablePermits():用于获取信号量中当前可用的许可数目。
5、Semaphore 结语
- 当信号量Semaphore初始化设置许可证为 1 时,它也可以当作互斥锁使用。其中 0、1 就相当于它的状态,当=1时 表示其他线程可以获取;当=0时 排他,即其他线程必须要等待。
- Semaphore是JUC包中的一个很简单的工具类,用来实现多线程下对于资源的同一时刻的访问线程数限制
- Semaphore中存在一个【许可】的概念,即访问资源之前,先要获得许可,如果当前许可数量为0,那么线程阻塞,直到获得许可。
- Semaphore内部使用AQS实现,由抽象内部类Sync继承了AQS。因为Semaphore天生就是共享的场景,所以其内部实际上类似于共享锁的实现。
- 共享锁的调用框架和独占锁很相似,它们最大的不同在于获取锁的逻辑——共享锁可以被多个线程同时持有,而独占锁同一时刻只能被一个线程持有。
- 由于共享锁同一时刻可以被多个线程持有,因此当头节点获取到共享锁时,可以立即唤醒后继节点来争锁,而不必等到释放锁的时候。因此,共享锁触发唤醒后继节点的行为可能有两处,一处在当前节点成功获得共享锁后,一处在当前节点释放共享锁后。
- 采用semaphore来进行限流的话会产生突刺现象。【突刺现象:指在一定时间内的一小段时间内就用完了所有资源,后大部分时间中无资源可用。比如在限流方法中的计算器算法,设置1s内的最大请求数为100,在前100ms已经有了100个请求,则后面900ms将无法处理请求。】
参考:https://cloud.tencent.com/developer/article/1801558