Simaphore,第一次了解这个名词,从字面意思看:信号灯、信号量,总是不太清楚表示何种意思,并不能够清楚将该概念与程序结合起
来,直到后面看到《并发编程艺术》针对该名词讲解,颇为贴切,引用放在文首。
信号量,将其看做马路上控制流量的信号灯,比如”学府路“想要控制人流量,每次只允许200辆车通过,其余车辆必须在路口等待,前200辆车拿到通过的许可证,可以开进马路,后面的车因为没有许可证不允许驶入“学府路”。如果其中有10辆车已经完全通过马路,哨兵重新颁发10张许可证给等待车辆,这10辆车允许通过。
上面的例子中,车辆就是工作线程、许可证可以当做是令牌,驶入马路表示线程在执行,完全通过马路表示线程执行完毕。
通过上面的例子可以总结出Simaphore概念的含义:用于控制同时访问特定资源的线程数,并且每个线程在获取锁之前必须从semaphore
获取许可(可以理解为令牌),当该线程获取锁之后执行完业务,将令牌释放回资源池中,其他线程可以重复利用。
信号量默认大小是1,表示只允许一个线程访问资源(也就是源码中用的permits,我理解翻译成资源比较合适),可以被用作互斥锁。这通常被称为二进制信号量,因为它只有两种状态:
还有一个资源可以被访问、零个资源可以被访问,
Semaphore类本身只实现了一个Serializable
接口,其内部有一个抽象内部类的Sync
同步组件,该类本身继承了AbstractQueuedSynchronizer
(以下都简称为AQS),以AQS为基础实现了自己的信号量同步器,之前提到过AQS中定义了通用的state,在Semaphore中表示可用资源数,Sync
类本身提供了一些方法供其两个实现类NonfairSync
和FairSync调用,保证Semaphore同样提供公平的获取资源及非公平的获取资源等两种模式,非公平模式下,资源利用的效率高于公平模式。
信号量的同步器 主要利用AQS实现资源控制。state表示可以获取的资源数,本身有两种继承类,主要为表示公平、非公平获取资源模式。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
/**
* 允许访问的资源数
* @param permits
*/
Sync(int permits) {
setState(permits);
}
/**
* 获取允许访问的资源数
* @return
*/
final int getPermits() {
return getState();
}
/**
* 非公平、共享的获取资源数
* 1. 首先获取所有可访问的子元素
* 2. 查看是否够本次访问
* 下次可用 = 当前可用 - acquires
* @param acquires 尝试获取的资源数
* @return 剩余可利用资源数
*/
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
/**
* 释放资源数
* 下次可用 = 当前可用 + releases
* @param releases 释放资源的个数
* @return 是否释放成功
*/
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
/**
* 减少资源数
* 下次可用 = 当前可用 - reductions
* @param reductions 减少的资源个数
*/
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
/**
* 将可用资源数降至为0
* @return 当前可用资源数
*/
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
Sync的非公平静态内部类,无论是构造方法还是获取资源的方法都是直接调用父类的方法,本身只是提供一种模式的保证。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
/**
* 初始化可用资源数 主要调用父类构造方法
* @param permits
*/
NonfairSync(int permits) {
super(permits);
}
/**
* 非公平下获取资源数
* @param acquires 获取资源数
* @return
*/
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
Sync的公平静态内部类
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
/**
* 初始化可用资源数 主要调用父类构造方法
* @param permits
*/
FairSync(int permits) {
super(permits);
}
/**
* 公平的获取资源数
* 1. 调用AQS方法判断,当前是否有等待线程在获取资源,如果有,则为了公平起见,暂时不能获取资源
* 2. 直到第一个条件不满足,查询可用资源数
* 3. 如果可用资源数大于零并且通过CAS设置可用资源成功,则直接返回,如果可用资源不足,直接返回。
* @param acquires 尝试获取的资源数
* @return
*/
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}
}
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
/**
* 初始化信号量对象
* @param permits 允许访问资源数
* @param fair 是否公平
*/
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
获取可用资源。该方法直接调用AQS的共享的、可中断的获取同步状态的方法。
如果获取不到资源则阻塞线程,直到有可用资源或者被其他线程中断,获取到资源之后,将资源池的可用资源数减1
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
非中断的获取资源。该方法直接调用AQS中的acquireShared()方法
如果没有资源可访问,则当前线程会一直等待,直到其他线程释放资源,资源池中有资源可访问。
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
尝试获取资源,非公平的抢占,不在乎是否有线程在等待。如果资产池有中资源可用则返回true,否则返回false
直接调用抽象内部类的非公平抢占式方法
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
如果在给定时间内有可用获取资源或者线程没有被中断,则线程尝试获取资源。 公平机制
本质是调用AQS中的tryAcquireSharedNanos(int arg, long nanosTimeout)
如果获取资源成功,则返回true,并且将资源池可用资源减1
如果没有可用资源线程会一直等待,直到出现以下三种情况:
1. 其他线程释放资源,并且当前线程是下一个等着获取资源的线程
2. 其他线程中断当前现车个
3. 超时
/**
* @param timeout 超时时间
* @param unit 超时时间单位
* @return
* @throws InterruptedException
*/
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
释放资源,资源池可用资源+1
方法实现是直接调用AQS中的releaseShared(int arg)
方法
public void release() {
sync.releaseShared(1);
}
获取特定数量资源 调用AQS中的acquireSharedInterruptibly(int arg)方法
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
获取特定数量资源(不可中断)
在可用资源够用之前,需要一直等待获取,即使当前线程被其他线程中断,也会一直阻塞等待
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
获取特定数量的资源数 非公平的获取
如果资源数不够,则直接返回false,放弃等待
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
带有超时的尝试获取特定资源数
本质是调用AQS中的tryAcquireSharedNanos(int arg, long nanosTimeout)方法
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
释放特定资源数
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
/**
* 可以利用的资源数
* @return
*/
public int availablePermits() {
return sync.getPermits();
}
/**
* 获取所有可利用的资源数,并将其降为0
* @return
*/
public int drainPermits() {
return sync.drainPermits();
}
/**
* 降低可用资源数
* @param reduction
*/
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
/**
* 判断当前获取资源的模式是否是公平模式 公平模式返回true,非公平模式返回false
* @return
*/
public boolean isFair() {
return sync instanceof FairSync;
}
/**
* 查询是否有线程在等待获取资源
* 同样是用于系统的监控
* @return
*/
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
/**
* 查询等待队列中等待线程数(估计值)
* 该方法同样用于监控系统的状态
* @return
*/
public final int getQueueLength() {
return sync.getQueueLength();
}
/**
* 查询所有等待队列中等待线程的集合
* 该方法同样用于监控系统的状态
* @return
*/
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
设置30个正在执行的线程,但是每次只允许10个并发线程执行,5s之后执行问,其他线程可以获取资源。
/**
* @time 2019/12/14 14:54
* @Description 信号量的例子
*
* 设置30个正在执行的线程,但是只允许10个并发线程执行
*/
public class SemaphoreTest {
private static final int THREAD_TOTAL = 30;
/**
* 初始化一个固定的线程池
*/
private static ExecutorService poolExecutor = new ThreadPoolExecutor(THREAD_TOTAL,
THREAD_TOTAL,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue <>(1024),
new ThreadPoolExecutor.AbortPolicy());
/**
* 初始化信号量
*/
static Semaphore semaphore = new Semaphore(10,false);
public static void main(String[] args) {
for(int i = 0; i < THREAD_TOTAL; i++){
poolExecutor.execute(()-> {
try {
semaphore.acquire();
System.out.println("允许获取资源,序号");
// 模拟工作1S
Thread.sleep(5000);
// 释放资源,然后
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
poolExecutor.shutdown();
}
}
Semaphore作用类似于开头所说,用于控制线程访问特定的资源,因此经常用与限流框架中、比如hystrix框架。