Java Semaphore(信号量)源码总结 AQS虚拟类的实现 从数据结构层面理解Semaphore限流原理


版本
JDK8(JDK1.8)

Semaphore类源码重点

  1. Semaphore(信号量)类有三个内部类Sync、NonfairSync、FairSync都继承自AQS(AbstractQueuedSynchronizer)
    AQS 源码可以看我这篇文章 AbstractQueuedSynchronizer

  2. AQS里面的state变量在不同AQS实现类有不同的意义,如在ReentrantLock中代表获取锁的次数,在Semaphore表示当前许可证的数量,即只有获取许可证的线程才可以执行自己的业务代码,否则只能进入AQS队列进行排队,而这恰好就是 Semaphore实现限流的原理
    ReentrantLock 源码可以看我这篇文章 ReentrantLock

  3. Semaphore实现限流的原理,假设一开始将AQS的state设为10即10个许可证,每个线程想要执行自己的代码先去获取许可证,获取一个许可证state数量就减一,减到0后如果还有线程想获取许可证,那是获取不到的,只能进去AQS的队列进行排队,直到排队排到队头才尝试获取许可证,当有获取许可证的线程执行完代码会释放许可证

  4. AQS虚拟类是双向链表实现的,链表中每个结点都对应一个线程,除了队头的线程是运行的,其他所有结点的线程几乎都是阻塞的,所以这就是叫阻塞阻塞队列的原因

Semaphore类具体实现

查询操作

availablePermits操作

作用:获取当前许可证的数量

public int availablePermits() {
   return sync.getPermits();
}
sync.getPermits操作

调用AQS的getState方法,获取AQS的state的值,state变量在不同AQS实现类有不同的意义,如在ReentrantLock中代表获取锁的次数,在Semaphore表示当前许可证的数量,即只有获取许可证的线程才可以执行自己的业务代码,否则只能进入AQS队列进行排队

final int getPermits() {
    return getState();
}

修改操作

reducePermits操作

作用:减少同时并发的线程数量
实现:减少AQS的state的值,state是当前许可证的数量,也可以理解为还能够让多少个线程并发,有一个线程获取许可证则会让state值减一,如果一开始state为10,那么刚好可以供10个线程并发,如果state为0了,只能等待并发的线程释放许可证才能有新线程能获取许可证

protected void reducePermits(int reduction) {
    if (reduction < 0) throw new IllegalArgumentException();
    	sync.reducePermits(reduction);
}

drainPermits操作

作用:清空所有许可证
实现:调用drainPermits自旋直到使用CAS将state设为0,或state本来就为0才退出自旋

public int drainPermits() {
    return sync.drainPermits();
}
sync.drainPermits操作

调用getState获取AQS的state的值,使用CAS将其设为0,CAS成功才返回,否则自旋

final int drainPermits() {
   for (;;) {
        int current = getState();
        if (current == 0 || compareAndSetState(current, 0))
            return current;
    }
}

Acquire操作(获取许可证)

acquire操作

作用:尝试获取许可证,没有成功则进入阻塞队列AQS排队
实现:使用AQS虚拟类的acquireSharedInterruptibly方法实现,传入的1是请求的许可证数量

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
acquireSharedInterruptibly
  1. 先判断是否有中断标志,如果有直接抛出异常中断线程
  2. 使用tryAcquireShared尝试获取许可证,如果没有成功,则执行doAcquireSharedInterruptibly,把当前线程当作结点插入AQS阻塞队列进行自旋排队
  3. 只有排队到队头才会重新尝试获取许可证,且每自旋一次(即循环一次)都会阻塞当前线程,同时每循环一次会检测该线程是否被打上了中断信号,是则中断
  4. tryAcquireShared是实现AQS子类需要实现的方法,作用是尝试获取共享锁,但它有两种实现一种公平锁,一种非公平锁,是怎样决定用哪个实现的呢,就在于上面acquire()方法的sync是 FairSync内部类的对象 还是 NonfairSync内部类的对象
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
     // 尝试获取许可证,如果没有成功
    if (tryAcquireShared(arg) < 0)
    	// 自旋排队
        doAcquireSharedInterruptibly(arg);
}
tryAcquireShared
  1. hasQueuedPredecessors判断AQS阻塞队列中有无线程在排队,如果有返回-1,获取失败,所以是公平锁
  2. getState()获取AQS中的state变量,state变量在不同AQS实现类有不同的意义,如在ReentrantLock中代表获取锁的次数,在Semaphore表示当前许可证的数量,即只有获取许可证的线程才可以执行自己的业务代码,否则只能进入AQS队列进行排队
  3. 如果剩余许可证数量remaining大于等于0,说明获取许可证成功可以执行业务代码,如果remaining小于0就是获取许可证失败可以直接跳出自旋(即循环)
  4. 使用CAS更新当前许可证的数量available为剩余许可证数量remaining,如果成功则跳出循环,不成功则重新执行逻辑
  5. 注意重新执行逻辑时会重新获取当前许可证的数量available,所以不是累减操作
公平锁版本
protected int tryAcquireShared(int acquires) {
	for (;;) {
	   // 如果有线程在排队
	   if (hasQueuedPredecessors())
	       return -1;
	   // 获取当前许可证的数量
	   int available = getState();
	   // 获取许可证剩余的数量
	   int remaining = available - acquires;
	   // 如果剩余许可证
	   if (remaining < 0 ||
	       // CAS更新当前许可证的数量为剩余许可证数量
	       compareAndSetState(available, remaining))
	       // 跳出自旋
	       return remaining;
	}
}
非公平锁版本

由于是非公平版本所以无须判断AQS阻塞队列中有无线程在排队,直接使用自己实现的方法,但是AQS的state值还是要使用的,因为当前许可证的数量存储在state中

protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}
nonfairTryAcquireShared操作
  1. 获取当前许可证的数量,计算获取许可证后剩余许可证的数量,和公平锁版本区别在于,无须使用hasQueuedPredecessors判断是否有线程在排队
  2. 其他和公平锁版本一模一样
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        // 获取当前许可证的数量
        int available = getState();
        // 获取剩余许可证的数量
        int remaining = available - acquires;
        if (remaining < 0 ||
     		 // CAS更新当前许可证的数量为剩余许可证数量
            compareAndSetState(available, remaining))
            // 返回剩余许可证的数量
            return remaining;
    }
}

acquire(int permits)操作

作用:尝试获取多个许可证,没有成功则进入阻塞队列AQS排队
实现:使用AQS虚拟类的acquireSharedInterruptibly方法实现,传入的permits是请求的许可证数量
和acquire()的区别只在于acquire()只获取一个许可证,而acquire(int permits)一次获取permits个许可证

public void acquire(int permits) throws InterruptedException {
     if (permits < 0) throw new IllegalArgumentException();
     sync.acquireSharedInterruptibly(permits);
 }

acquireUninterruptibly操作

作用:尝试获取1个许可证,没有成功则进入阻塞队列AQS排队
实现:使用AQS虚拟类的acquireShared方法实现,传入的1是请求的许可证数量,虽然叫Uninterruptibly不可中断,但其实AQS内部acquireShared还是会判断是否中断标志,有则自我中断

public void acquireUninterruptibly() {
   sync.acquireShared(1);
}
acquireShared操作
  1. tryAcquireShared尝试获取共享锁,有两种实现公平锁和非公平锁,上面讲过的,如果获取锁没有成功则执行doAcquireShared
  2. doAcquireShared方法将该线程作为节点添加进AQS同步队列排队,同时自旋(死循环)一直判断当前节点是否排队到队头了,到了则重新尝试获取共享锁,没有到或获取失败则阻塞当前线程,等待前一个节点的唤醒,AQS队列中前一个结点负责唤醒后一个结点的线程,只有队头获取到锁,当队头释放锁的时候会唤醒后一个线程去抢锁
public final void acquireShared(int arg) {
	//尝试获取共享锁
   if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

acquireUninterruptibly(int permits)操作

作用:尝试获取permits个许可证,没有成功则进入阻塞队列AQS排队
实现:使用AQS虚拟类的acquireShared方法实现,传入的permits是请求的许可证数量,和acquireUninterruptibly()唯一的区别就是,acquireUninterruptibly()只获取一个许可证,而它获取 permits个许可证

public void acquireUninterruptibly(int permits) {
   if (permits < 0) throw new IllegalArgumentException();
    sync.acquireShared(permits);
}

Release操作(释放许可证)

release操作

作用:释放许可证,1表示释放的许可证的数量
实现:调用AQS的releaseShared进行释放操作

public void release() {
   sync.releaseShared(1);
}
releaseShared操作
  1. 使用tryReleaseShared尝试释放arg数量的许可证,如果释放成功,则执行doReleaseShared唤醒AQS队列后续一个结点
  2. 唤醒的结点会去尝试去获取许可证再执行自己的业务逻辑,因为每个结点对应一个线程
  3. tryReleaseShared是AQS子类自己实现的
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
tryReleaseShared操作
  1. 获取当前许可证的数量current,计算释放许可证后的许可证的数量next,如果释放后许可证数量反而减少则抛出异常
  2. 使用CAS更新当前许可证的数量state为新的值next,如果更新成功跳出自旋,返回成功,如果没有成功则自旋直到成功为止
  3. CAS失败的原因,因为在代码执行过程中其他线程获取许可证或释放许可证改变了state的值,使得state不等于current
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;
     }
 }

release(int permits)操作

作用:释放permits个许可证
实现:和release操作几乎一模一样

 public void release(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.releaseShared(permits);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lolxxs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值