什么是Semphore
java.util.concurrent.Semaphore 类是一个计数信号量。计数信号量由一个指定数量的 “许可” 初始化。每调用一次 acquire(),一个许可会被调用线程取走。每调用一次 release(),一个许可会被返还给信号量。因此,在没有任何 release() 调用时,最多有 N 个线程能够通过 acquire() 方法,N 是该信号量初始化时的许可的指定数量。这些许可只是一个简单的计数器。
主要常用方法
acquire()
1 | public void acquire() throws InterruptedException |
从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。获取一个许可(如果提供了一个)并立即返回,将可用的许可数减 1。如果没有可用的许可,则在发生以下两种情况之一前,禁止将当前线程用于线程安排目的并使其处于休眠状态
某些其他线程调用此信号量的 release() 方法,并且当前线程是下一个要被分配许可的线程
其他某些线程中断当前线程
这里说明一下红色字样的具体含义,当许可不够时,又有多个线程竞争许可,不能保证当前线程一定会是下一个被分配许可的线程。
没有办法保证线程能够公平地可从信号量中获得许可。也就是说,无法担保掉第一个调用 acquire()
的线程会是第一个获得一个许可的线程。如果第一个线程在等待一个许可时发生阻塞,而第二个线程前来索要一个许可的时候刚好有一个许可被释放出来,那么它就可能会在第一个线程之前获得许可。
如果你想要强制公平,Semaphore 类有一个具有一个布尔类型的参数的构造子,通过这个参数以告知 Semaphore
是否要强制公平。强制公平会影响到并发性能,所以除非你确实需要它否则不要启用它。
1 | Semaphore semaphore = new Semaphore(1, true); |
release()
1 | public void release() |
release()释放一个许可,将其返回给信号量。释放一个许可,将可用的许可数增加 1。如果任意线程试图获取许可,则选中一个线程并将刚刚释放的许可给予它。然后针对线程安排目的启用(或再启用)该线程。
availablePermits()
1 | public int availablePermits() |
availablePermits()返回此信号量中当前可用的许可数
示例:
这里还是利用之前提到的客服场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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通话结束……
延伸示例
这里示例会给出一个秒杀场景,性能有待验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | 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,非常遗憾,秒杀失败…