目录
Semaphore许可证
Semaphore是一个线程同步工具,主要用于同一时刻允许一定数量的线程对共享资源并行操作的场景。
Semaphore可以用于限制用户人数类似的一些场景,比如停车场、高峰吃饭时的饭店位置等。
我们先通过一个例子了解它的用法:
public static void main(String[] args) {
//限制10个
final int permits = 10;
//定义10个Semaphore许可证
final Semaphore semaphore = new Semaphore(permits, true);
//启动20个线程
IntStream.range(0,20).forEach(i -> {
new Thread(()->{
boolean flag = semaphore.tryAcquire();
if (flag){
System.out.println(Thread.currentThread().getName()+"获取许可证");
}else {
System.out.println(Thread.currentThread().getName()+"没有获取许可证");
}
try {
//模拟获取许可证之后的业务处理
sleep();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName()+"释放获取许可证");
}
},"线程-"+i).start();
});
}
private static void sleep() {
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(3));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
上述代码中,勾勾创建了10个许可,也即是最多10个线程同时运行。
通过有参构造制定了Semaphore对象的许可证数量和获取许可证的方法为公平方式。
tryAcquire()是获取许可证的方法,该方法不会使线程阻塞,获取到许可证返回true,获取不到返回false。
release()获取许可证的线程在运行结束之后需释放许可证,为了保证许可证的释放,release()方法一般写在finally语句块中。
Semaphore原理分析
Semaphore的实现原理是什么的,接下来就和勾勾一起看源码吧。
Semaphore是基于AQS共享模式的实现,其维护的内部类实现了AbstractQueuedSynchronizer。
Semaphore在构造时指定许可证的数量即是指定了AQS同步器状态state的值,所以在Semaphore中state表示许可证的数量。
同时Semaphore构造时还可以指定获取许可证的公平或者非公平的方式,默认是非公平的获取方式,
Semaphore重写了共享模式下获取许可tryAcquireShared()和释放许可tryReleaseShared()方法,其内部类NonfairSync和FairSync实现不同的tryAcquireShared方法。
内部类Sync源码注释如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
//有参构造,permits即是state的值
Sync(int permits) {
setState(permits);
}
//获取许可证数量,即获取state同步器状态值
final int getPermits() {
return getState();
}
//非公平方式获取操作
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
//获取剩余许可证数量
int available = getState();
//将剩余许可证减去当前线程想要获取的许可证数量
int remaining = available - acquires;
//如果小于0,则返回剩余的许可证数量,是个负值
//如果不小于0,则通过CAS修改state的值,修改成功则返回剩余数量,是个正值
//如果CAS失败,则一直循环
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//共享方式释放操作
protected final boolean tryReleaseShared(int releases) {
for (;;) {
//获取state的值
int current = getState();
//将state的值加上当前线程要释放的许可数量
int next = current + releases;
//如果释放之后的许可数量小于当前数量,则抛异常
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
//cas修改state的值,修改成功返回true,失败返回false
if (compareAndSetState(current, next))
return true;
}
}
}
NonfairSync非公平类,其获取许可的操作直接调用了父类Sync的nonfairTryAcquireShared()方法。
/**
* NonFair version
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
FairSync公平类,其自己实现了tryAcquireShared()方法。
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
//判断当前线程节点是否为下一个要唤醒的节点,不是则返回-1
if (hasQueuedPredecessors())
return -1;
//如果是下一个要唤醒的节点,则尝试获取许可
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
我们之前《AQS之共享模式》一文中学习了AQS共享模式的原理,我们了解到tryAcquireShared()方法返回小于0的数值则表示线程获取许可失败,返回大于0的数值则表示线程许可成功。
获取失败之后会怎么样呢?
Semaphore基本流程:
Semaphore方法总结
-
Semaphore构造器
Semaphore(int permits):定义Semaphore指定许可数量,并且默认非公平的方式获取许可。
Semaphore(int permits, boolean fair):定义Semaphore指定许可数量以及同步方式。
-
获取方法
acquire():当前线程调用此方法获取许可证,获取不到会进入阻塞状态一直等待,直到Semaphore有可用的许可或者被其他线程打断。如果Semaphore有可用的许可,该方法会立即返回。
acquire(int permits):当前线程调用该方法会向Semaphore获取指定permits的许可数量,permits不能小于0。该方法作用与acquire()一样,只是指定了获取的许可数量,acquire()是获取一个许可数量。
acquireUninterruptibly():当前线程调用此方法获取许可,如果获取不到会一直阻塞而且不能被打断,直到Semaphore有可用许可才能退出阻塞。此方法一般不用,因为可能会导致大规模的线程阻塞而导致Java进程假死现象。
acquireUninterruptibly(int permits):该方法与acquireUninterruptibly()作用一样,只是可以指定获取permits个许可数量,permits不能小于0。
tryAcquire():尝试获取许可数量,只会向Semaphore申请一个许可数量,如果Semaphore的许可数量大于等于1,将会获取成功返回true,否则将会获取许可失败返回false。
tryAcquire(long timeout, TimeUnit unit):尝试获取许可数量,也是只申请一个许可,但是它增加了超时参数。如果在超时的时间内还是没有可用的许可,当前线程会进入阻塞状态,直到达到超时时间或者超时时间内获得了许可,或者阻塞线程被其他线程打断。
tryAcquire(int permits):该方法与tryAcquire()作用一样,只是可以指定获取permits个许可数量,permits不能小于0。
tryAcquire(int permits, long timeout, TimeUnit unit):该方法可以获取permits个许可数量,并且可以设置超时时间。
-
释放操作
release():释放一个许可证,Semaphore内部的许可证数量会加1,表示多了1个可用的许可。
release(int permits):释放permits个许可证,Semaphore内部的许可证数量会加permits。permits不能小于0。
Semaphore总结
Semaphore可以允许一定数量的线程对共享资源进行访问,并且提供了丰富的获取操作:阻塞或者不阻塞,中断或者不中断、获取多个许可或者一个许可。
Semaphore虽然可以控制并发的线程数量,但是对共享资源的数据安全并没有提供任何保证,所以如果涉及到了共享可变资源的并发需要额外的控制。
如果在开发中使用了不阻塞的方式获取许可数量,需对返回结果进行处理,否则可能出现尝试获取许可失败却依然执行了业务逻辑。
好了今天就到这里,祝福大家周末愉快 !!!
下一篇文章是最后一个JUC工具类Phaser,哇哇哇,看到光明了!!!