什么是Semphore
java.util.concurrent.Semaphore 类是一个计数信号量。计数信号量由一个指定数量的 “许可” 初始化。每调用一次 acquire(),一个许可会被调用线程取走。每调用一次 release(),一个许可会被返还给信号量。因此,在没有任何 release() 调用时,最多有 N 个线程能够通过 acquire() 方法,N 是该信号量初始化时的许可的指定数量。这些许可只是一个简单的计数器。
主要常用方法
acquire()
public void acquire() throws InterruptedException
从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。获取一个许可(如果提供了一个)并立即返回,将可用的许可数减 1。如果没有可用的许可,则在发生以下两种情况之一前,禁止将当前线程用于线程安排目的并使其处于休眠状态
某些其他线程调用此信号量的 release() 方法,并且当前线程是下一个要被分配许可的线程
其他某些线程中断当前线程
这里说明一下红色字样的具体含义,当许可不够时,又有多个线程竞争许可,不能保证当前线程一定会是下一个被分配许可的线程。
没有办法保证线程能够公平地可从信号量中获得许可。也就是说,无法担保掉第一个调用 acquire()
的线程会是第一个获得一个许可的线程。如果第一个线程在等待一个许可时发生阻塞,而第二个线程前来索要一个许可的时候刚好有一个许可被释放出来,那么它就可能会在第一个线程之前获得许可。
如果你想要强制公平,Semaphore 类有一个具有一个布尔类型的参数的构造子,通过这个参数以告知 Semaphore
是否要强制公平。强制公平会影响到并发性能,所以除非你确实需要它否则不要启用它。
Semaphore semaphore = new Semaphore(1, true);
release()
public void release()
release()释放一个许可,将其返回给信号量。释放一个许可,将可用的许可数增加 1。如果任意线程试图获取许可,则选中一个线程并将刚刚释放的许可给予它。然后针对线程安排目的启用(或再启用)该线程。
availablePermits()
public int availablePermits()
availablePermits()返回此信号量中当前可用的许可数
示例:
这里还是利用之前提到的客服场景
package com.yvan.semaphore;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;/*** Semaphore* * @author yvan**/
public class AppMain {
// 客服数private static final int _SERVICER = 2;// 用户数private static final int _CUSTOMER = 10;public static void main(String[] args) throws Exception {
Semaphore semaphore = new Semaphore(_SERVICER);ExecutorService executorService = Executors.newFixedThreadPool(_CUSTOMER);for (int i = 0; i < _CUSTOMER; i++) {
executorService.execute(new Processer(semaphore, "客户" + i));}executorService.shutdown();}
}class Processer implements Runnable {
private Semaphore semaphore;private String user;public Processer(Semaphore semaphore, String user) {
super();this.semaphore = semaphore;this.user = user;}@Overridepublic void run() {
try {
// 获取当前许可// 场景中就是空闲的客服人员数int free = semaphore.availablePermits();if (free <= 0) {
System.out.println("客服坐席正忙,请您耐心等待......");}// 获取许可,没有许可就等待semaphore.acquire();System.out.println(user + "已经接入,正在通话中......");// 模拟通话时长TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {
e.printStackTrace();} finally {
System.out.println(user + "通话结束......");// 释放许可semaphore.release();}}
}
结果
客户0已经接入,正在通话中……
客服坐席正忙,请您耐心等待……
客户1已经接入,正在通话中……
客服坐席正忙,请您耐心等待……
客服坐席正忙,请您耐心等待……
客服坐席正忙,请您耐心等待……
客服坐席正忙,请您耐心等待……
客服坐席正忙,请您耐心等待……
客服坐席正忙,请您耐心等待……
客服坐席正忙,请您耐心等待……
客户0通话结束……
客户1通话结束……
客户2已经接入,正在通话中……
客户3已经接入,正在通话中……
客户2通话结束……
客户4已经接入,正在通话中……
客户3通话结束……
客户5已经接入,正在通话中……
客户4通话结束……
客户6已经接入,正在通话中……
客户5通话结束……
客户7已经接入,正在通话中……
客户6通话结束……
客户8已经接入,正在通话中……
客户7通话结束……
客户9已经接入,正在通话中……
客户8通话结束……
客户9通话结束……
延伸示例
这里示例会给出一个秒杀场景,性能有待验证
package com.yvan.semaphore;import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/*** Semaphore 秒杀* * @author yvan**/
public class Seckill {
// 秒杀商品数public static int _COUNT=7;// 并发秒杀用户数public static final int _CUSTOMER=10;public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);ExecutorService executorService = Executors.newFixedThreadPool(_CUSTOMER);for (int i = 0; i < _CUSTOMER; i++) {
executorService.execute(new SeckillProcess(semaphore, "用户"+i));}executorService.shutdown();}
}class SeckillProcess implements Runnable{
private Semaphore semaphore ;private String user;public SeckillProcess(Semaphore semaphore, String user) {
super();this.semaphore = semaphore;this.user = user;}@Overridepublic void run() {
try {
// 模拟用户客户端网络连接情况TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));semaphore.acquire();if (Seckill._COUNT>0) {
Seckill._COUNT--;// 模拟后续秒杀业务执行时间TimeUnit.MILLISECONDS.sleep(100);System.out.println("恭喜"+user+"秒杀成功");}else {
System.out.println(user+",非常遗憾,秒杀失败...");}} catch (InterruptedException e) {
e.printStackTrace();}finally {
semaphore.release();}}}
结果
恭喜用户4秒杀成功
恭喜用户6秒杀成功
恭喜用户2秒杀成功
恭喜用户7秒杀成功
恭喜用户0秒杀成功
恭喜用户3秒杀成功
恭喜用户9秒杀成功
用户5,非常遗憾,秒杀失败…
用户1,非常遗憾,秒杀失败…
用户8,非常遗憾,秒杀失败…