概述
原理
Semaphore(计数信号量)用来控制同时访问特定资源的线程数量,通过协调多线程,确保资源被合理使用。正常的锁在任何时刻都是只允许一个任务访问一项资源,而Semaphore允许多个任务同时访问一项资源。某些程度上和线程池有些相似,可用的数量固定,线程使用完后,剩下的进行排队,当有线程使用完毕后,放回线程池,然后再分配给排队的任务进行使用。
作用
Semaphore用来限制线程并发数量,用于限流,对访问的资源进行数量限制,比如:视频账号只允许最多两端同时在线,手机和电脑可以同时在线,若是想在平板上登录,手机和电脑中必须有一端退出登录才行;疫苗接种房间最多同时接种10人,接种者进入房间前发放许可证,只有手持许可证才能进入房间接种,10张许可证发放完后,剩下的人排队等着,接种完后收回许可证发放给后面排队的人进行接种。
主要方法描述
public Semaphore(int permits)
permits:获取许可线程的数量,默认是不公平的设置。public Semaphore(int permits, boolean fair)
permits:获取许可线程的数量,fair:true表示线程启动顺序和获得许可的顺序,先启动的先获取,当然只是理论保证,实际中无法确保100%,false表示线程启动顺序和获得许可顺序无关,有些线程快,有些线程慢,每次都会不一样。acquire()
获取许可,也就是消耗一个许可。acquire(int permits)
每次调用消耗permits个许可。acquireUninterruptibly()
消耗一个许可,不允许等待线程中断。acquireUninterruptibly(int permits)
一次获取permits个许可,且等待线程不被中断。tryAcquire()
尝试获取许可,并不阻塞线程,获取失败返回false。tryAcquire(long timeout, TimeUnit unit)
指定时间内获取许可,没有获取成功只要在指定时间内就轮询获取,超时获取失败返回false,不阻塞线程。release()
释放一个许可,并唤醒一个正在阻塞的线程。release(int permits)
一次释放permits许可。availablePermits()
获取可用许可数量。drainPermits()
获取并返回所有可用许可,并将可用许可置为0。reducePermits(int reduction)
减少可用许可数量,不阻塞线程。getQueueLength()
返回正在等待获取许可的线程数量。hasQueuedThreads()
查询是否还有正在等待获取许可的线程。
使用
// 5张许可证
final Semaphore semaphore = new Semaphore(5);
// 使用线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
int finalI = i;
Runnable runnable = () -> {
try {
// 获取许可
semaphore.acquire();
System.out.println("第" + finalI + "位开始接种");
// 睡眠
Thread.sleep(new Random().nextInt(10000));
System.out.println("第" + finalI + "位接种完毕");
// 释放许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
executorService.execute(runnable);
}
executorService.shutdown();
结果:
第0位开始接种
第1位开始接种
第2位开始接种
第3位开始接种
第4位开始接种
第3位接种完毕
第8位开始接种
第4位接种完毕
第5位开始接种
第1位接种完毕
第9位开始接种
第8位接种完毕
第7位开始接种
第5位接种完毕
第6位开始接种
第7位接种完毕
第0位接种完毕
第2位接种完毕
第6位接种完毕
第9位接种完毕
观察结果发现,0、1,2、3、4满5位后就没有开始接种的,只有第3位接种完毕后,空出一个位置,这个位置给了第8位,第4位接种完毕后空位给了第5位,第1位接种完毕后空出的位置给了第9位,总而言之,就是空出一位顶上一位,直到所有人接种完毕。