一、概述
在之前学习的synchronized关键字的对象锁与ReentrantLock重入锁都只能允许一个线程获取锁。而Semaphore不同,它可以允许多个线程获取锁访问资源,Semaphore常被运用在限流等应用场景。
二、常用API
构造方法
Semaphore(int permits) : 参数表示许可数量
Semaphore(int permits,boolean fair): 当fair等于true时,设置为公平信号量,默认是非公平的
常用方法
- void acquire() throws InterruptedException:获取一个许可,当许可数量为0的时候,当前线程阻塞,该操作可以响应中断
- void acquireUninterruptibly(int permits) :和acquire(int permits) 方法类似,只是不会响应线程中断
- boolean tryAcquire():尝试获取1个许可,不管是否能够获取成功,都立即返回,true表示获取成功,false表示获取失败
- boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException:尝试在指定的时间内获取1个许可,获取成功返回true,指定的时间过后还是无法获取许可,返回false
- void release():释放一个许可,将其返回给信号量
- int availablePermits():当前可用的许可数
三、案例
1.简单使用
public class Demo1 {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
Thread t = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"等待获取许可");
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"获取许可,执行业务逻辑5秒");
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName()+"释放许可");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.setName("线程"+i);
t.start();
}
}
}
执行效果:
线程0等待获取许可
线程0获取许可,执行业务逻辑5秒
线程1等待获取许可
线程1获取许可,执行业务逻辑5秒
线程2等待获取许可
线程2获取许可,执行业务逻辑5秒
线程3等待获取许可
线程4等待获取许可
线程5等待获取许可
线程6等待获取许可
线程7等待获取许可
线程8等待获取许可
线程9等待获取许可
线程0释放许可
线程1释放许可
线程2释放许可
线程3获取许可,执行业务逻辑5秒
线程4获取许可,执行业务逻辑5秒
线程5获取许可,执行业务逻辑5秒
线程3释放许可
线程5释放许可
线程4释放许可
线程6获取许可,执行业务逻辑5秒
线程7获取许可,执行业务逻辑5秒
线程8获取许可,执行业务逻辑5秒
线程7释放许可
线程6释放许可
线程8释放许可
线程9获取许可,执行业务逻辑5秒
线程9释放许可
可以看到每次最多同时只有三个线程能执行代码块。
2. 正确释放锁
在上述的例子中,如果在执行逻辑代码过程中遇到了异常,就不能释放锁,将会导致排队的线程永远得不到许可。
Thread t = new Thread(()->{
boolean flag = false;
try {
System.out.println(Thread.currentThread().getName()+"等待获取许可");
semaphore.acquire();
flag = true;
System.out.println(Thread.currentThread().getName()+"获取许可,执行业务逻辑5秒");
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName()+"释放许可");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if (flag)
semaphore.release();
}
});
使用一个boolean类型的变量flag控制,是为了防止在acquire()过程中发生异常,也进行锁的释放,从而对导致信号量的许可数量增加
3.在规定时间内获取许可
信号量的tryAcquire()方法,可以设置超时等待时间
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
Thread t = new Thread(()->{
boolean flag = false;
try {
System.out.println(Thread.currentThread().getName()+"等待获取许可");
flag = semaphore.tryAcquire(2,TimeUnit.SECONDS);
if (flag){
System.out.println(Thread.currentThread().getName()+"获取许可,执行业务逻辑6秒");
TimeUnit.SECONDS.sleep(6);
}else {
System.out.println(Thread.currentThread().getName()+"获取许可超时,失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if (flag){
System.out.println(Thread.currentThread().getName()+"释放许可");
semaphore.release();
}
}
});
t.setName("线程"+i);
t.start();
TimeUnit.SECONDS.sleep(1);
}
}
}
执行效果:
线程0等待获取许可
线程0获取许可,执行业务逻辑6秒
线程1等待获取许可
线程1获取许可,执行业务逻辑6秒
线程2等待获取许可
线程2获取许可,执行业务逻辑6秒
线程3等待获取许可
线程4等待获取许可
线程3获取许可超时,失败
线程5等待获取许可
线程0释放许可
线程4获取许可,执行业务逻辑6秒
线程6等待获取许可
线程1释放许可
线程5获取许可,执行业务逻辑6秒
线程7等待获取许可
线程2释放许可
线程6获取许可,执行业务逻辑6秒
线程8等待获取许可
线程7获取许可超时,失败
线程9等待获取许可
线程8获取许可超时,失败
线程9获取许可超时,失败
线程4释放许可
线程5释放许可
线程6释放许可