1、概念
Semaphore(信号量)是一个线程同步工具,主要用于在一个时刻允许多个线程对共享资源进行并行操作的场景。通常情况下,使用Semaphore的过程实际上是多个线程获取访问共享资源许可证的过程。
Semaphore内部逻辑:
- 如果此时Semaphore内部的计数器大于零,那么线程将可以获得小于该计数器数量的许可证,同时还会导致Semaphore内部的计数器减少所发放的许可证数量。
- 如果此时Semaphore内部的计数器等于0,也就是说没有可用的许可证,那么当前线程有可能会被阻塞(使用tryAcquire方法时不会阻塞)。
- 当线程不再使用许可证时,需要立即将其释放以供其他线程使用,所以建议将许可证的获取以及释放动作写在try…finally语句块中。
2、Semaphore使用示例
这里模拟最大人数的场景,当人数达到上限收,后续的线程就无法进入,当有信号量被释放后,后续的线程可以继续进入。
public class SemaphoreTest {
public static void main(String[] args) {
final LoginService loginService = new LoginService(5);
for (int i=0;i<10;i++){
Thread thread = new Thread(()->{
if(loginService.login()){
try {
//模拟游玩时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName() + ",开始游玩!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
loginService.logout();
}
}else{
System.out.println(Thread.currentThread().getName() + "人满,回家睡觉!");
}
});
thread.start();
}
}
private static class LoginService{
private final Semaphore semaphore;
public LoginService(int maxUser){
this.semaphore = new Semaphore(maxUser,true);
}
public boolean login(){
if(semaphore.tryAcquire()){
System.out.println(Thread.currentThread().getName() + ",进园成功!");
return true;
}
return false;
}
public void logout(){
semaphore.release();
System.out.println(Thread.currentThread().getName() + ",出园!");
}
}
}
Thread-0,进园成功!
Thread-1,进园成功!
Thread-2,进园成功!
Thread-3,进园成功!
Thread-4,进园成功!
Thread-5人满,回家睡觉!
Thread-6人满,回家睡觉!
Thread-3,开始游玩!
Thread-3,出园!
Thread-7,进园成功!
Thread-9人满,回家睡觉!
Thread-8人满,回家睡觉!
Thread-0,开始游玩!
Thread-0,出园!
Thread-1,开始游玩!
Thread-1,出园!
Thread-7,开始游玩!
Thread-7,出园!
Thread-4,开始游玩!
Thread-4,出园!
Thread-2,开始游玩!
Thread-2,出园!
在上述示例中,线程“Thread-7”等到有是否资源后,又可以获取进入资格了。
release方法: 在Semaphore设计中,并未强制要求执行release操作的线程必须是执行了acquire的线程才可以,而是需要开发人员自身具有相应的编程约束来确保Semaphore的正确使用,这样就可能造成不是获取到信号量的线程调用了release方法,从而造成信号量的错误释放。错误示例如下,本来主线程不应该获取到信号量,但是实际上因为t2线程的中断,执行了finally 的中的release方法,造成了信号量的错误释放。
public class Semaphore2Test {
public static void main(String[] args) throws InterruptedException {
//定义信号量
final Semaphore semaphore = new Semaphore(1,true);
Thread t1 = new Thread(()->{
try{
semaphore.acquire();
System.out.println("t1 获取到资源!");
TimeUnit.HOURS.sleep(1);
}catch (Exception e){
System.out.println("t1 当前线程被中断!");
}finally {
semaphore.release();
}
});
Thread t2 = new Thread(()->{
try{
semaphore.acquire();
System.out.println("t2 获取到资源!");
}catch (Exception e){
System.out.println("t2 当前线程被中断!");
}finally {
semaphore.release();
}
});
t1.start();
TimeUnit.SECONDS.sleep(2);
t2.start();
TimeUnit.SECONDS.sleep(2);
t2.interrupt();
semaphore.acquire();
System.out.println("主线程获取资源访问权限!");
}
}
3、Semaphore常用方法
1、构造函数
有两个构造函数。其中,所谓的公平信号量是获得锁的顺序与线程启动顺序有关,但不代表100%地获得信号量,仅仅是在概率上能得到保证。而非公平信号量就是无关的。
- public Semaphore(int permits):定义Semaphore指定许可证数量,并且指定非公平的同步器,因此new Semaphore(n)实际上是等价于new Semaphore(n,false)的。
- public Semaphore(int permits, boolean fair):定义Semaphore指定许可证数量的同时给定非公平或是公平同步器。
2、tryAcquire方法
tryAcquire方法尝试向Semaphore获取许可证,如果此时许可证的数量少于申请的数量,则对应的线程会立即返回,结果为false表示申请失败,tryAcquire包含如下:
- tryAcquire():尝试获取Semaphore的许可证,该方法只会向Semaphore申请一个许可证,在Semaphore内部的可用许可证数量大于等于1的情况下,许可证将会获取成功,反之获取许可证则会失败,并且返回结果为false。
- boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException:该方法与tryAcquire无参方法类似,同样也是尝试获取一个许可证,但是增加了超时参数。如果在超时时间内还是没有可用的许可证,那么线程就会进入阻塞状态,直到到达超时时间或者在超时时间内有可用的证书(被其他线程释放的证书),或者阻塞中的线程被其他线程执行了中断。
- boolean tryAcquire(int permits):在使用无参的tryAcquire时只会向Semaphore尝试获取一个许可证,但是该方法会向Semaphore尝试获取指定数目的许可证。
- boolean tryAcquire(int permits, long timeout, TimeUnit unit):该方法与第二个方法类似,只不过其可以指定尝试获取许可证数量的参数
3、acquire方法
acquire方法也是向Semaphore获取许可证,但是该方法比较偏执一些,获取不到就会一直等(陷入阻塞状态),但是可以被其他线程中断,Semaphore为我们提供了acquire方法的两种重载形式:
- void acquire():该方法会向Semaphore获取一个许可证,如果获取不到就会一直等待,直到Semaphore有可用的许可证为止,或者被其他线程中断。当然,如果有可用的许可证则会立即返回。
- void acquire(int permits):该方法会向Semaphore获取指定数量的许可证,如果获取不到就会一直等待,直到Semaphore有可用的相应数量的许可证为止,或者被其他线程中断。同样,如果有可用的permits个许可证则会立即返回。
4、acquireUninterruptibly方法
和acquire方法相比,不会被其他线程中断。
- void acquireUninterruptibly():该方法会向Semaphore获取一个许可证,如果获取不到就会一直等待,与此同时对该线程的任何中断操作都会被无视,直到Semaphore有可用的许可证为止。当然,如果有可用的许可证则会立即返回。
- void acquireUninterruptibly(int permits):该方法会向Semaphore获取指定数量的许可证,如果获取不到就会一直等待,与此同时对该线程的任何中断操作都会被无视,直到Semaphore有可用的许可证为止,或者被其他线程中断。同样,如果有可用的permits个许可证则会立即返回。
5、release方法
release方法需要正确使用,避免出现上述提到的情况。
- void release():释放一个许可证,并且在Semaphore的内部,可用许可证的计数器会随之加一,表明当前有一个新的许可证可被使用。
- void release(int permits):释放指定数量(permits)的许可证,并且在Semaphore内部,可用许可证的计数器会随之增加permits个,表明当前又有permits个许可证可被使用。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68708.html