什么是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()
释放一个许可,将其返回给信号量。释放一个许可,将可用的许可数增加 1。如果任意线程试图获取许可,则选中一个线程并将刚刚释放的许可给予它。然后针对线程安排目的启用(或再启用)该线程。
availablePermits()
public int 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;
}
@Override
public 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;
}
@Override
public 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,非常遗憾,秒杀失败…