怎么深入理解JUC中的Semaphore(什么是juc)

技术怎么深入理解JUC中的Semaphore怎么深入理解JUC中的Semaphore,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。前面我们分

如何深刻理解JUC的Semaphore,很多新手对此不是很清楚。为了帮助大家解决这个问题,下面小编就为大家详细讲解一下。有这种需求的可以借鉴,希望你能有所收获。

前面,我们分析了同步器CountDownLatch和CyclicBarrier的设计和实现。两个同步器在使用中有一个共同的特点,就是构造时要指定参与线程的数量,然后计数器要贬值。本文将要介绍的Semaphore在构造时也需要指定一个int类型的权限参数,但是这个参数并不用于指定参与线程的数量。相反,Semaphore不限制参与线程的数量,它用于限制允许同时执行的线程的最大数量。

如果参与Semaphore的线程想要继续运行,它需要从Semaphore申请一个或多个令牌。只有成功获取令牌的线程才允许继续执行,否则需要阻塞等待并在执行后返回令牌。参数权限可以理解为令牌的总数。只要信号量有可用的令牌,就允许应用新的线程。一个线程一次可以申请一个或多个令牌。只要有足够的令牌,Semaphore就允许多个线程同时并行执行。

00-1010下面是一个排队吃饭的例子,演示了Semaphore的基本用法。假设一家餐厅一次最多只能容纳五个人。但是因为菜味道很好,生意很好,来吃饭的人数超过了餐厅同时能容纳的最大人数,所以超过名额的需要在外面排队等电话。假设今天有20个人来吃饭,打电话的过程可以实现如下:

privatestaticfinaintmax _ COUNT=5;

privatedstaticclasspersoncomplementrunnable {

privateSemaphoresemaphore

publicPerson(信号量)

this.semaphore=信号量;

}

@覆盖

public void run(){ 0

尝试{

system . out . println(' Thread ' Thread . currentthread()。getName()'正在等待。);

semaphore . acquire();

system . out . println(' Thread ' Thread . currentthread()。getName()“正在运行。”);

时间单位。seconds . sleep(randomutils . nextint(1,3));

system . out . println(' Thread ' Thread . currentthread()。getName()“ateup”);

} catch(exception one){ 0

e . printstacktrace();

        } finally {
            semaphore.release();
        }

    }
}

public static void main(String[] args) {
    // 使用公平锁,保证叫号尽量的公平
    Semaphore semaphore = new Semaphore(MAX_COUNT, true);
    for (int i = 0; i < 20; i++) {
        new Thread(new Person(semaphore), String.valueOf(i)).start();
    }
}

上述示例中当一个顾客到达时需要调用 Semaphore#acquire 方法申请获取令牌(即询问是否有空位),如果没有空闲的令牌则需要等待。当一个顾客就餐完毕之后需要归还之前申请到的令牌(执行 Semaphore#release 方法),此时允许下一位顾客申请令牌进入餐厅就餐。

Semaphore 实现内幕

下面来看一下 Semaphore 的设计与实现。Semaphore 同样基于 AQS 实现,其内部类 Sync 继承自 AbstractQueuedSynchronizer,并派生出 FairSync 和 NonfairSync 两个子类,分别表示公平锁和非公平锁,这些设计与前面文章中介绍的基于 AQS 实现的 ReentrantLock 和 ReentrantReadWriteLock 如出一辙。Semaphore 在构造时允许我们通过参数指定是使用公平锁还是非公平锁,默认为非公平锁,如下:

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

Semaphore 中定义的方法在实现上均委托给 Sync 对象执行,并复用 AQS 的 state 字段记录当前剩余可用的令牌数。下面重点来分析一下 Semaphore 申请和归还令牌的方法实现,即 Semaphore#acquireSemaphore#release 方法。首先来看一下令牌申请的过程,Semaphore 提供了多个版本的 Semaphore#acquire 方法实现,包括:

  • Semaphore#acquire():申请 1 个令牌,如果成功则立即返回,否则阻塞等待,期间支持响应中断请求。

  • Semaphore#acquire(int):相对于 Semaphore#acquire() 的区别在于一次性申请多个令牌。

  • Semaphore#acquireUninterruptibly():申请 1 个令牌,如果成功则立即返回,否则阻塞等待,期间忽略中断请求。

  • Semaphore#acquireUninterruptibly(int):相对于 Semaphore#acquireUninterruptibly() 的区别在于一次性申请多个令牌。

  • Semaphore#tryAcquire():尝试申请 1 个令牌,不管成功还是失败都会立即返回,成功则返回 true,失败则返回 false。

  • Semaphore#tryAcquire(int):相对于 Semaphore#tryAcquire() 的区别在于一次性申请多个令牌。

  • Semaphore#tryAcquire(long, TimeUnit):尝试申请 1 个令牌,相对于 Semaphore#tryAcquire() 引入了超时等待机制。

  • Semaphore#tryAcquire(int, long, TimeUnit):相对于 Semaphore#tryAcquire(long, TimeUnit) 的区别在于一次性申请多个令牌。

这些申请令牌的方法在实现上大同小异,下面以 Semaphore#acquire() 为例分析一下具体的执行过程。方法实现如下:

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

上述方法直接委托给 AQS 的 AbstractQueuedSynchronizer#acquireSharedInterruptibly 方法执行,申请令牌单位为 1。前面在分析 AQS 时已经介绍了该方法的运行机制,下面重点来看一下 Sync 对于模板方法 AbstractQueuedSynchronizer#tryAcquireShared 的实现(以 NonfairSync 为例):

// NonfairSync#tryAcquireShared
protected int tryAcquireShared(int acquires) {
    return this.nonfairTryAcquireShared(acquires);
}

// Sync#nonfairTryAcquireShared
final int nonfairTryAcquireShared(int acquires) {
    for (; ; ) {
        // 获取 state 状态值
        int available = this.getState();
        // 计算剩余可用的资源数
        int remaining = available - acquires;
        if (remaining < 0 // 当前没有可用的资源
                || this.compareAndSetState(available, remaining)) { // 当前有可用的资源,且获取资源成功
            return remaining;
        }
    }
}

申请令牌的执行流程可以总结为:

  1. 获取当前剩余可用的令牌数,即 state 值;

  2. 如果剩余可用的令牌数小于本次申请的数目,则返回差值(负值);

  3. 否则,更新 state 值,如果更新成功则说明获取令牌成功,返回差值(非负值)。

AbstractQueuedSynchronizer#acquireSharedInterruptibly 方法的实现我们知道,如果上述过程返回负值,则会将当前线程添加到同步队列中阻塞等待。

再来看一下令牌归还的过程,Semaphore 同样提供了多个版本的 Semaphore#release 方法实现,包括:

  • Semaphore#release():归还 1 个令牌。

  • Semaphore#release(int):归还指定数目的令牌。

下面以 Semaphore#release() 方法为例分析一下令牌归还的执行过程,实现如下:

public void release() {
    sync.releaseShared(1);
}

上述方法直接委托给 AQS 的 AbstractQueuedSynchronizer#releaseShared 方法执行,归还令牌单位为 1。前面在分析 AQS 时同样已经介绍了该方法的运行机制,下面重点来看一下 Sync 对于模板方法 AbstractQueuedSynchronizer#tryReleaseShared 的实现:

protected final boolean tryReleaseShared(int releases) {
    for (; ; ) {
        // 获取 state 状态值
        int current = this.getState();
        // 计算释放之后剩余的资源数
        int next = current + releases;
        if (next < current) { // overflow
            // 溢出
            throw new Error("Maximum permit count exceeded");
        }
        // 更新 state 状态值
        if (this.compareAndSetState(current, next)) {
            return true;
        }
    }
}

令牌归还的执行过程如上述代码注释,比较简单,但是有一点疑问的是什么情况下 next 值会溢出?

一般来说线程在归还令牌之前必须先申请令牌,这样就能够保证空闲令牌的数量始终不会大于我们在构造 Semaphore 时指定的初始值,然而在上述方法实现中,我们并没有看到有任何逻辑限定在调用 Semaphore#release 方法之前必须调用 Semaphore#acquire 方法。实际上 Semaphore 在实现时也的确没有添加这一限制,也就说任何线程都可以调用 Semaphore#release 方法归还令牌,即使它之前从来没有申请过令牌,这样就会导致令牌的数量溢出。官方文档中有如下说明:

There is no requirement that a thread that releases a permit must have acquired that permit by calling {@link #acquire}. Correct usage of a semaphore is established by programming convention in the application.

也就是说,Semaphore 并不要求线程在归还令牌之前一定要先申请获取令牌,具体由应用程序自己决定。

我们分析了 Semaphore 信号量的设计与实现,了解到 Semaphore 同样是基于 AQS 实现的同步器组件。Semaphore 通过令牌机制以限定参与的线程在同一时间执行的线程数目不能超过令牌的个数,在语义和实现上都比较简单,但功能却很强大。最后还需要注意的一点就是,Semaphore 并不要求在归还令牌之前一定要先申请获取令牌,开发者可以结合自身业务逻辑来灵活应用这一点。

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注行业资讯频道,感谢您对的支持。

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/153165.html

(0)

相关推荐

  • 顺序志愿,学考志愿填报顺序重要吗

    技术顺序志愿,学考志愿填报顺序重要吗重要的,一般是按顺序录取,所以第一志愿很重要顺序志愿。考生要按照招生专业对选考科目的要求填报志愿,只有符合选考科目要求才能报考相应专业。考生须熟知平行志愿投档原则。平行志愿投档时按照“

    生活 2021年10月31日
  • Laravel基于reset怎么实现分布式事务

    技术Laravel基于reset怎么实现分布式事务这篇文章主要讲解了“Laravel基于reset怎么实现分布式事务”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Lara

    攻略 2021年11月9日
  • 租用香港服务器从事游戏相关业务有什么好处

    技术租用香港服务器从事游戏相关业务有什么好处香港服务器提供了部署所需游戏的灵活性和能力。它可以多方面提升游戏体验,并通过丰富的资源为您的服务器提供更好的安全性,因为您是独享硬件配置的。这可能解释了为什么现代游戏玩家和主播

    礼包 2021年12月21日
  • CSS学习笔记:定位属性position

    技术CSS学习笔记:定位属性position CSS学习笔记:定位属性position目录一、定位属性简介二、各属性值的具体功能1. relative2. absolute3. fixed三、三种定位属

    礼包 2021年11月4日
  • 怎么进行MongoDB和Cassandra以及HBase三种NoSQL数据库比较

    技术怎么进行MongoDB和Cassandra以及HBase三种NoSQL数据库比较本篇文章给大家分享的是有关怎么进行MongoDB和Cassandra以及HBase三种NoSQL数据库比较,小编觉得挺实用的,因此分享给

    攻略 2021年11月3日
  • golang 占位符%d %t %v

    技术golang 占位符%d %t %v golang 占位符%d %t %v1、首先需要了解哪些占位符分别代表什么
    这些是死知识,把常用的记住,不常用的直接查表就行了
    golang 的fmt 包实现了

    礼包 2021年11月30日