Semaphore(信号量)用来控制并发访问的线程数量,它通过协调各个线程来保证资源的合理使用。其内部使用的是AQS机制(concurrent包下很多类实现原理都是基于AQS)。Semaphore实现控制并发线程数可以抽象为停车场模型,一个固定车位的停车场,当车位满了,便不再允许新的车辆进入;若当前车库驶出多少辆,则就允许进入多少辆。Semaphore做的就是监控车位大小功能。通过构造函数new Semaphore(N)来实现,N表示最多并发访问的线程数。所以当初始化创建了5个许可证时,如果多余5个线程并发访问则阻塞排队等候,至于是否公平访问,可以灵活的设置new Semaphore(int permits,boolean fair),当前线程acquire()一次许可证就减少一个,release()一次就归还一个。
Semaphore应用场景
例如有些方法很耗时:需要一次性读取10万条数据,并按照某些规则进行计算,然后写入csv文件中,提供给管理员下载,由于这种方法很耗机器资源,就需要对这个方法进行限制同时调用的线程数,下面以50个管理员并发调用为例,每次至多允许3个人同时调用:
/**
* @desc:信号量测试:50个管理员同时调用某个很耗时的接口,至多允许三个管理员同时调用,其余的排队等候
**/
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3, true);
ExecutorService executor = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
executor.execute(() -> {
try {
semaphore.acquire();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "号管理员获取许可,任务开始");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + "号管理员释放许可,任务结束");
}
});
}
executor.shutdown();
}
}
//输出结果:
pool-1-thread-2号管理员获取许可,任务开始
pool-1-thread-3号管理员获取许可,任务开始
pool-1-thread-1号管理员获取许可,任务开始
pool-1-thread-3号管理员释放许可,任务结束
pool-1-thread-2号管理员释放许可,任务结束
pool-1-thread-1号管理员释放许可,任务结束
pool-1-thread-5号管理员获取许可,任务开始
pool-1-thread-6号管理员获取许可,任务开始
pool-1-thread-4号管理员获取许可,任务开始
……
例如IO密集型操作,开启了30个工作线程并发的读取文件,然后进行持久化操作,但是数据库最大连接数为20,如果不控制,就出现获取数据库连接失败错误,通过Semaphore控制并发连接数据库线程数:
public class SemaphoreTest {
static ExecutorService executor = Executors.newFixedThreadPool(30);
static Semaphore semaphore = new Semaphore(20, true);
public static void main(String[] args) {
for (int i = 0; i < 30; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
semaphore.tryAcquire(); //获取许可证
// TODO something work
System.out.println("current thread " + Thread.currentThread().getId() + " time=" + System.currentTimeMillis());
semaphore.release(); //归还许可证
}
});
}
executor.shutdown();
}
}
需要注意的是,acquire()允许一次性获取多个信号量,比如semaphore.acquire(3)表示一次性获取3个,但是在释放的时候也得释放三个semaphore.release(3),否则许可证就越来越少了。