文章目录
源码地址:https://github.com/nieandsun/concurrent-study.git
该工具类,在并发编程中主要用来进行并发控制,或者说限流
1 原理简介
Semaphore是信号的意思,但是我觉得将其理解为许可
或者说令牌好像更好理解一些,其原理可以用下图进行表示:
即n个线程都想抢占运行某段代码,但是在它们运行之前必须得先去获取指定数量的许可,比如说:
- (1)总共的许可就3个,哪个线程想要运行这段代码,必须获取1个许可
- 假如某个时间线程1、线程2、线程3获取到了许可,则这三个线程就可以运行这段代码了。其他没获取到的,必须等这三个线程中有一个释放了许可,其他线程才能拿到许可,并执行这段代码 —> 即并发数为3。
再比如说:
- (2)总共的许可还是就3个,哪个线程想要运行这段代码,必须获取3个许可
- 那么假如某个时间线程1获取到了3个许可(
注意:
如果是指定必须获取3个许可,那么不会有线程一次只拿到1个或2个的情况,即要么拿到3个,要么1个没拿到
),那其他线程就没法获取许可了,这个时候同一时刻,就只能有一个线程运行该方法了 —》即不存在并发的情况了。
我
自认为
通过上面两个栗子大家肯定知道Semaphore的使用原理了。
2 基本使用方法
2.1 demo1 — 每次获取一个许可,将线程并发数控制为N个
- code
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
public class SemaphoreDemo1 {
private final static int threadCount = 12;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
semaphore.acquire(); //获取一个许可
//总共3个许可,而每次只需拿到一个许可就可以运行下面的方法
//也就是说同一时刻可以允许三个线程拿到许可,即并发数为3
test(threadNum);
semaphore.release(); //释放一个许可
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int i) throws InterruptedException {
Thread.sleep(2000);
log.info("threadNum:{}", i);
}
}
- 测试结果
2.2 demo2 — 每次获取多个许可(或者说所有可获取的许可),使线程并发数变为1
- code
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
public class SemaphoreDemo2 {
private final static int threadCount = 4;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
semaphore.acquire(3); //获取3个许可
//总共3个许可,每次都全获取了
//也就是说同一时刻只允许一个线程拿到许可,即并发数为1
test(threadNum);
semaphore.release(3); //释放3个许可
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int i) throws InterruptedException {
Thread.sleep(2000);
log.info("threadNum:{}", i);
}
}
- 测试结果:
3 其他玩法
Semaphore提供的方法还挺多的,如下图所示,但大都大同小异。相信大家看看源码中的注释肯定就知道是干什么的了。 我这里再做两个小demo演示一下tryAcquire的用法,其他用法大家可以自行探索。
如上图所示,tryAcquire方法有四种使用姿势:
- tryAcquire() — 尝试获取一个许可
- tryAcquire(long timeout, TimeUnit unit) — 尝试在某个时间段内获取一个许可
- tryAcquire(int permits) — 尝试获取多个许可
- tryAcquire(int permits, long timeout, TimeUnit unit) — 尝试在某个时间段内获取多个许可
看到这里我觉得大家应该都直接会用了。。。☺☺☺
需要注意的一点是:
这些方法的返回值都是boolean ,这也就提示我们,当线程竞争特别激烈
的时候,我们完全可以让某些线程尝试获取一下执行权,如果实在获取不到,为了更好地保护我们的系统,我们就直接将其舍弃 —要非常注意一下这里的舍弃
。
下面举两个栗子,简单演示一下其用法。
3.1 demo3 — 尝试获取许可,如果获取不到,直接舍弃 ★★★
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
public class SemaphoreDemo3 {
private final static int threadCount = 520;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
if (semaphore.tryAcquire()) { //尝试获取1个许可
//总共3个许可,而每次只需拿到一个许可就可以运行下面的方法
//也就是说同一时刻可以允许三个线程拿到许可,即并发数为3
test(threadNum);
semaphore.release(); //释放3个许可
}
});
}
exec.shutdown();
}
private static void test(int i) {
try {
Thread.sleep(1); //这里即使注释掉也是一样的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("threadNum:{}", i);
}
}
在不看运行结果时,你会不会认为能运行test方法的线程数在(3,520)的
开区间内
,---- 即一开始会有3个线程抢到许可,然后1毫秒之后他们释放掉许可,其他还没开启的线程正好又可以抢到许可???
but,事实并非如此,运行结果如下:
由这个结果我们可以看到,事实是只要有一个线程没抢到运行的许可,其他线程就也别想再运行了。。。
是不是很神奇!!! —> 有兴趣的看源码研究到底为什么吧,我这里就不深入了。
3.2 demo4 — 尝试一段时间内获取许可,如果获取不到,直接舍弃 ★★★
- code
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@Slf4j
public class SemaphoreDemo4 {
private final static int threadCount = 520;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
//5秒内尝试获取1个许可
if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
test(threadNum);
semaphore.release(); //释放3个许可
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
exec.shutdown();
}
private static void test(int i) {
try {
Thread.sleep(1000); //这里即使注释掉也是一样的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("threadNum:{}", i);
}
}
- 运行结果如下:
由此可知,加上时间也是只要有一个线程尝试获取不到许可了,其他线程就也别想再运行了。。。
end