深入浅出Semaphore源码解析
Semaphore通过permits的值来限制线程访问临界资源的总数,属于有限制次数的共享锁,不支持重入。
前提条件
在理解Semaphore时需要具备一些基本的知识:
理解AQS的实现原理
之前有写过一篇《深入浅出AQS源码解析》关于AQS的文章,对AQS原理不了解的同学可以先看一下
Semaphore源码解析
Semaphore中有3个内部类,分别是Sync、NonfairSync和FairSync,Sync是NonfairSync和FairSync的抽象类,我们会从解读Semaphore实现的功能开始入手逐渐去解析Sync、NonfairSync和FairSync的源码
public class Semaphore implements java.io.Serializable {
    private final Sync sync;
    /**
     * 初始化permits个资源
     */
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    /**
     * 初始化permits个资源,根据fair来决定是使用公平锁还是非公平锁的方式
     */
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
    /**
     * 中断方式获取一个资源
     */
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    /**
     * 非中断方式获取一个资源
     */
    public void acquireUninterruptibly() {
        sync.acquireShared(1);
    }
    /**
     * 尝试获取一个资源
     */
    public boolean tryAcquire() {
        return sync.nonfairTryAcquireShared(1) >= 0;
    }
    /**
     * 尝试超时获取一个资源
     */
    public boolean tryAcquire(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    /**
     * 释放一个资源
     */
    public void release() {
        sync.releaseShared(1);
    }
    /**
     * 中断方式获取permits个资源
     */
    public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
    }
    /**
     * 非中断方式获取permits个资源
     */
    public void acquireUninterruptibly(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireShared(permits);
    }
    /**
     * 尝试获取permits个资源
     */
    public boolean tryAcquire(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        return sync.nonfairTryAcquireShared(permits) >= 0;
    }
    /**
     * 尝试超时获取permits个资源
     */
    public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
        throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
    }
    /**
     * 释放permits个资源
     */
    public void release(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.releaseShared(permits);
    }
    /**
     * 获取当前可用资源数量
     */
    public int availablePermits() {
        return sync.getPermits();
    }
    /**
     * 用掉所有的资源,并返回用掉的资源数量
     */
    public int drainPermits() {
        return sync.drainPermits();
    }
    /**
     * 缩减reduction个资源
     */
    protected void reducePermits(int reduction) {
        if (reduction < 0) throw new IllegalArgumentException();
        sync.reducePermits(reduction);
    }
}
虽然Semaphore中的方法比较多,但是都比较简单,都是转调用Sync中的方法,通过解析Sync中的源码来帮助大家理解这些方法是如何实现的
Sync类源码解析
abstract static class Sync extends AbstractQueuedSynchronizer {
    // 获取所有可用的资源数量
    final int getPermits() {
        return getState();
    }
    // 非公平的方式尝试获取acquires个可用的资源
    final int nonfairTryAcquireShared(int acquires) {
        // 无限循环,尝试获取acquires个资源
        // 如果资源数量不够,返回剩余资源数量
        // 如果资源数量足够且获取成功,返回剩余的资源数量
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
    // 尝试获取releases个资源
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            int next = current + releases;
            // 当releases不允许为负数
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            // CAS操作尝试修改state的值
            if (compareAndSetState(current, next))
                return true;
        }
    }
    // 缩减releases个资源
    final void reducePermits(int reductions) {
        for (;;) {
            int current = getState();
            int next = current - reductions;
            // 当releases不允许为负数,也就时不能通过该方法增加资源
            if (next > current) // underflow
                throw new Error("Permit count underflow");
            // CAS操作尝试修改state的值
            if (compareAndSetState(current, next))
                return;
        }
    }
    // 清空所有的资源数量
    final int drainPermits() {
        for (;;) {
            int current = getState();
            // CAS操作尝试将资源数量设置为0
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}
FairSync类源码解析
FairSync中的源码很简单,直接上代码
static final class FairSync extends Sync {
    FairSync(int permits) {
        super(permits);
    }
    protected int tryAcquireShared(int acquires) {
        /**
         * 具体思路如下:
         * 1、如果AQS的同步队列中有等待的线程,直接获取失败,会加入到AQS的同步队列中
         * 2、如果AQS的同步队列为空,尝试修改state的值来获取acquires个资源
         * 3、一直重复步骤1和2,直到有结果返回才退出无限循环
         */
        for (;;) {
            if (hasQueuedPredecessors())
                return -1;
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}
NonfairSync类源码解析
NonfairSync中的源码就更简单,解析如下:
static final class NonfairSync extends Sync {
    NonfairSync(int permits) {
        super(permits);
    }
    // 抢占式的获取acquires个资源
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}
总结
- 当
permits初始化为1时,Semaphore变成了一个互斥的排他锁 - 当
permits初始化为无穷大时,Semaphore变成了无锁模式 - 当
state的值为0的时候,无法获取资源,获取资源的线程会进入AQS的同步队列等待有资源释放时被唤醒 - 当
Semaphore初始化成非公平锁时,可能会出现有的线程饿死的情况,一般对于控制资源的使用而言,建议初始化为公平锁 - 可以调用
reducePermits动态的缩减资源的数量,但是不能增加资源的数量 
深入浅出Semaphore源码解析的更多相关文章
- 深入浅出ReentrantLock源码解析
		
ReentrantLock不但是可重入锁,而且还是公平或非公平锁,在工作中会经常使用到,将自己对这两种锁的理解记录下来,希望对大家有帮助. 前提条件 在理解ReentrantLock时需要具备一些基本 ...
 - 深入浅出ReentrantReadWriteLock源码解析
		
读写锁实现逻辑相对比较复杂,但是却是一个经常使用到的功能,希望将我对ReentrantReadWriteLock的源码的理解记录下来,可以对大家有帮助 前提条件 在理解ReentrantReadWri ...
 - 死磕 java同步系列之Semaphore源码解析
		
问题 (1)Semaphore是什么? (2)Semaphore具有哪些特性? (3)Semaphore通常使用在什么场景中? (4)Semaphore的许可次数是否可以动态增减? (5)Semaph ...
 - Java并发之Semaphore源码解析(一)
		
Semaphore 前情提要:在学习本章前,需要先了解笔者先前讲解过的ReentrantLock源码解析,ReentrantLock源码解析里介绍的方法有很多是本章的铺垫.下面,我们进入本章正题Sem ...
 - Java并发之Semaphore源码解析(二)
		
在上一章,我们学习了信号量(Semaphore)是如何请求许可证的,下面我们来看看要如何归还许可证. 可以看到当我们要归还许可证时,不论是调用release()或是release(int permit ...
 - Java并发包源码学习系列:同步组件Semaphore源码解析
		
目录 Semaphore概述及案例学习 类图结构及重要字段 void acquire() 非公平 公平策略 void acquire(int permits) void acquireUninterr ...
 - 深入浅出AQS源码解析
		
最近一直在研究AQS的源码,希望可以更深刻的理解AQS的实现原理.虽然网上有很多关于AQS的源码分析,但是看完以后感觉还是一知半解.于是,我将自己的整个理解过程记录下来了,希望对大家有所帮助. 基本原 ...
 - 死磕 java同步系列之CyclicBarrier源码解析——有图有真相
		
问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...
 - 死磕 java同步系列之Phaser源码解析
		
问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...
 
随机推荐
- arduino连接1602LCD方法
			
arduino连接1602LCD方法 参考代码:
 - C++ vector迭代器访问二维数组
			
#include<iostream> #include<vector> int main(){ std::vector<int> arr(); // 创建一维数组 ...
 - Vuex怎么用(1)
			
1. vuex是什么 github站点: https://github.com/vuejs/vuex在线文档: https://vuex.vuejs.org/zh-cn/简单来说: 对应用中组件的状态 ...
 - LeetCode 79,这道走迷宫问题为什么不能用宽搜呢?
			
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题第48篇文章,我们一起来看看LeetCode当中的第79题,搜索单词(Word Search). 这一题官方给的难 ...
 - sharding-jdbc源码解析
			
参考博客:https://cloud.tencent.com/developer/article/1529692 看sharding-jdbc支持XA协议重点看下面的代码 sharding-trans ...
 - 你的 IDEA 是如何配置的?卡不卡?试试这样配置
			
本文作者在和同事的一次讨论中发现,对 IntelliJ IDEA 内存采用不同的设置方案,会对 IDE 的速度和响应能力产生不同的影响. Don't be a Scrooge and give you ...
 - C++的逗号运算符
			
逗号运算符( , )是C++语言运算符中优先级最低的一种运算符,结合顺序是从左至右,用来顺序求值(最后一个逗号后面表达式的值作为整个表达式的值). 感觉这个东西还是挺冷门的,之前都不知道,平时也比较少 ...
 - 状态机模式 与 ajax 的结合运用
			
太神奇了,昨晚做了个梦,梦中我悟出一个道理:凡是涉及到异步操作而且需要返回值的函数,一定要封装成 Promise 的形式,假如返回值取决于多个异步操作的结果,那么需要对每个异步操作进行状态的设计,而且 ...
 - vim/vm命令后提示错误:Found a swap file by the name ".dockerfile.swp"
			
今天在使用docker时,使用vim命令操作dockerfile文件,提示如下错误: 错误原因,是由于上一次在操作该文件时,异常退出,然后系统生成了一个dockerfile.swp文件,该文件是个隐藏 ...
 - spring quartz 每30分钟执行一次cronExpression表达式怎么写
			
<cron-expression>0 0/30 * * * ?</cron-expression>:每隔30分钟 <cron-expression>0 0/15 ...