Semaphore:信号标; 旗语。
Java中表示可以控制线程去访问资源,也可以反馈线程在指定时间内是否可以获得可执行权的信号,从而对这些超时未获取执行权利的线程做另行安排。
Semaphore 的简单使用
acquire() 和 release()
private static void SemaphoreDemo1() {
final Semaphore semaphore = new Semaphore(2);//1
IntStream.range(0,4).forEach(i ->{
new Thread(() ->{
System.out.println(Thread.currentThread().getName() + "开始");
try {
semaphore.acquire();//2
System.out.println(Thread.currentThread().getName() + "获取许可证");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException interruptedException){
interruptedException.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放许可证");
semaphore.release();//3
}
System.out.println(Thread.currentThread().getName() + "结束");
},"thread" + (i + 1)).start();
});
}
-
初始化一个持有两个信号的Semaphore;
-
调用acquire()方法获取一个信号;
- acquire()方法每次只能获取一个信号
-
调用release()方法释放一个信号
- release()方法每次只能释放一个信号
上述代码中有四个线程,执行结果是每次只能有两个线程获取到信号,休眠三秒释放信号后其他线程才可以获取到释放的信号;执行结果如下:
thread1开始
thread3开始
thread4开始
thread2开始
thread3获取许可证
thread1获取许可证
休眠中...
休眠中...
thread1释放许可证
thread3释放许可证
thread3结束
thread1结束
thread2获取许可证
休眠中...
thread4获取许可证
休眠中...
thread4释放许可证
thread2释放许可证
thread2结束
thread4结束
Semaphore 会被interrupted中断
private static void SemaphoreDemo2() throws InterruptedException {
final Semaphore semaphore = new Semaphore(1);//1
Thread thread = new Thread(() ->{
try {
semaphore.acquire(2);//2
}catch (InterruptedException e){
e.printStackTrace();
}
});
thread.start();
TimeUnit.MICROSECONDS.sleep(500);
thread.interrupt();//3
}
- 初始化一个只有一个信号的Semaphore
- 调用acquire(2)获取两个,但是此时只有一个信号,所以线程thread会阻塞
- main方法调用thread的interrupt()会中断线程,出现异常
- semaphore#acquireUninterruptibly() 则不会出现中断异常,而是会一直阻塞
执行结果如下:
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:998)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
at java.util.concurrent.Semaphore.acquire(Semaphore.java:467)
at main.Test.lambda$SemaphoreDemo2$2(Test.java:75)
at java.lang.Thread.run(Thread.java:748)
drainPermits() 一次性获取所有许可证
private static void SemaphoreDemo4() {
final Semaphore semaphore = new Semaphore(10);
new Thread(() ->{
System.out.println("availablePermits:" + semaphore.availablePermits());
semaphore.drainPermits();//一次性获取所有许可证
System.out.println("availablePermits:" + semaphore.availablePermits());
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}
semaphore.release(5);
System.out.println(Thread.currentThread().getName() + "结束");
},"thread").start();
}
业务案例:
由上述API得知,Semaphore可以控制工作线程的数量,我们可以联想到在微服务中网关限流等操作;业务模拟:
public class T extends Thread {
Semaphore semaphore;
public T (Semaphore semaphore,String name){
super(name);
this.semaphore=semaphore;
}
@Override
public void run() {
try {
//尝试在100毫秒内获取信号
boolean b = semaphore.tryAcquire(100, TimeUnit.MILLISECONDS);
if (b){
System.out.println("线程:"+Thread.currentThread().getName() +"获取到信号,执行任务");
//休眠3秒,模拟执行任务
TimeUnit.SECONDS.sleep(3);
semaphore.release();
} else {
System.out.println("线程:"+Thread.currentThread().getName()+"获取信号失败");
rollback();
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚程序,进行降级
*/
private void rollback() {
System.out.println("线程:"+Thread.currentThread().getName()+"未获取到信号,程序降级");
}
}
模拟每次只能有四个线程执行任务:
Semaphore semaphore = new Semaphore(4);
for (int i = 0; i < 10; i++) {
new Thread(new T(semaphore,"线程"+i)).start();
}
输出结果:
线程:Thread-5获取到信号,执行任务
线程:Thread-9获取到信号,执行任务
线程:Thread-4获取到信号,执行任务
线程:Thread-8获取到信号,执行任务
线程:Thread-1获取信号失败
线程:Thread-0获取信号失败
线程:Thread-7获取信号失败
线程:Thread-7未获取到信号,程序降级
线程:Thread-2获取信号失败
线程:Thread-2未获取到信号,程序降级
线程:Thread-3获取信号失败
线程:Thread-3未获取到信号,程序降级
线程:Thread-6获取信号失败
线程:Thread-6未获取到信号,程序降级
线程:Thread-0未获取到信号,程序降级
线程:Thread-1未获取到信号,程序降级
总结下Semaphore常用的方法:
| 方法 | 描述 |
|---|---|
acquire() | 获取一个许可证,可以被打断,没有足够的许可证时阻塞等待 |
acquire(int permits) | 获取指定数量的许可证,可以被打断,没有足够的许可证时阻塞等待 |
acquireUninterruptibly() | 获取一个许可证,不可被打断,没有足够的许可证时阻塞等待 |
acquireUninterruptibly(int permits) | 获取指定数量的许可证,不可被打断,没有足够的许可证时阻塞等待 |
tryAcquire() | 尝试获取一个许可证,没有足够的许可证时程序继续执行,不会被阻塞 |
tryAcquire(int permits) | 尝试获取指定数量的许可证,没有足够的许可证时程序继续执行,不会被阻塞 |
tryAcquire(long timeout, TimeUnit unit) | 在指定的时间范围内尝试获取1个许可证,没有足够的许可证时程序继续执行, 不会被阻塞,在该时间方位内可以被打断 |
tryAcquire(int permits, long timeout, TimeUnit unit) | 在指定的时间范围内尝试获取指定数量的许可证,没有足够的许可证时程序 继续执行,不会被阻塞,在该时间方位内可以被打断 |
release() | 释放一个许可证 |
drainPermits() | 一次性获取所有可用的许可证 |
availablePermits() | 获取当前可用许可证数量的预估值 |
hasQueuedThreads() | 判断是否有处于等待获取许可证状态的线程 |
getQueueLength() | 获取处于等待获取许可证状态的线程的数量的预估值 |
getQueuedThreads() | 获取处于等待获取许可证状态的线程集合 |