怎么深入理解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)

相关推荐

  • ThreadPoolExecutor线程池的示例分析

    技术ThreadPoolExecutor线程池的示例分析小编给大家分享一下ThreadPoolExecutor线程池的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获

    攻略 2021年11月17日
  • 论文翻译:2020GCRNLearning Complex Spectral Mapping With Gated Convolutional Recurrent Networks for Monaural Speech Enhancement

    技术论文翻译:2020GCRNLearning Complex Spectral Mapping With Gated Convolutional Recurrent Networks for Monaural Spee

    礼包 2021年12月9日
  • Confluence怎么扩展内存

    技术Confluence怎么扩展内存这篇文章主要介绍了Confluence怎么扩展内存,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。背景:Confluen

    攻略 2021年11月17日
  • php7.2运行失败怎么解决

    技术php7.2运行失败怎么解决本篇内容主要讲解“php7.2运行失败怎么解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“php7.2运行失败怎么解决”吧!

    攻略 2021年12月9日
  • windows中信号量和互斥量的区别是什么

    技术windows中信号量和互斥量的区别是什么本篇内容介绍了“windows中信号量和互斥量的区别是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希

    2021年11月9日
  • go语言如何处理TCP拆包/粘包

    技术go语言如何处理TCP拆包/粘包这篇文章主要讲解了“go语言如何处理TCP拆包/粘包”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go语言如何处理TCP拆包/粘包”吧

    攻略 2021年12月9日