Semaphore实现Andoird版源代码剖析

本文深入探讨Semaphore信号量的概念、用途、构造函数、acquire和release接口实现,以及内部类Sync的详细实现。重点分析了Semaphore如何通过控制资源访问的并发线程数,实现线程间的互斥和同步,特别强调了公平性和非公平性在不同场景下的应用考量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Semaphore是一个计数的信号量。从概念上来说,信号量维持一组许可(permits)。acquire方法在必须的时候都会堵塞直到有一个许可可用,然后就会拿走这个许可。release方法加入一个许可,会有可能释放一个堵塞中的获取者(acquirer)。然而,Semaphore没有使用真实的许可对象,仅仅是保持一个可用计数而且採取对应的行为。

信号量一般用于限制能够訪问一些(物理上或者逻辑上)的资源的并发线程数。

    信号量初始化为1的时候,意味着它最多仅仅有一个同意可用,这样就能作为相互排斥独占锁使用。这样的很多其它地被称为二进制信号量(binary semaphore),由于它仅仅有两个状态:一个许可可用,或者0个许可可用。当使用这样的方式的时候,二进制信号量就有这样的属性(不像大部分锁的实现):锁能够被拥有者(就如信号量没有拥有者的概念)之外的另外线程释放。这样的属性在某些特殊的上下文中非常实用,比如死锁恢复。
    类的构造函数可选地接受一个fairness參数。当设为false的时候,该类就不会保证线程获取许可的顺序。特别地,插队是同意的,也就是说,线程调用acquire方法能够在另外的等待线程之前分配许可————逻辑上新线程会把自己放在等待线程队列头。当fairness设为true,信号量就保证调用acquire方法的线程会以它们调用方法的处理顺序来获取许可(FIFO)。注意FIFO的顺序决定特指在这些方法的内部运行点。因此,有可能一条线程在另外一条线程之前调用了acquire方法,但实际顺序会在另外一条线程之后,相同也适用于函数返回的先后顺序。相同要注意非超时版本号tryAcquire方法不会遵循fairness设置,会立即获取不论什么可用的许可。
    一般来说,信号量用来控制资源訪问的话,就应该被初始化为公平(fairness设为true),这样能够确保没有线程会在訪问资源的时候饿坏。当使用在其它同步控制的情况下使用信号量,非公平的吞吐量优势一般优于公平的考虑。

    事实上整体上来看,fairness參数以及状态量的概念非常接近AQS(AbstractQueuedSynchronizer)提供的功能,因此大家也应该猜到Semaphore的内部实现也是通过一个继承AQS的内部类实现接口功能。接下来我们细致看看内部的实现。

详细实现

    先来看看Semaphore的构造函数:
    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }


    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
    能够看到构造函数与ReentrantLock实现类似,都是依照fair參数分配创建不同的锁类,再来看看Semaphore的acquire和release的接口实现
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }


    public void release() {
        sync.releaseShared(1);
    }
    能够看到acquire和release的实现都是调用内部类Sync的方法实现,当然了,这些方法也就是AQS提供出来的获取和释放共享锁接口。接下来看看整个实现里最基本的内部类Sync的相关实现:
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;


        Sync(int permits) {
            setState(permits);
        }


        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }


        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;
            }
        }

        //省略一些次要方法
    }


    /**
     * 非公平版本号
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;


        NonfairSync(int permits) {
            super(permits);
        }


        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }


    /**
     * 公平版本号
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;


        FairSync(int permits) {
            super(permits);
        }


        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;
            }
        }
    }
    为了方便了解主要逻辑,Sync类省略掉一些次要的方法。非公平版本号NonfairSync类和公平版本号FairSync类都继承于Sync类,Sync类继承于AQS类,NonfairSync和FairSync类都有相同的tryReleaseShared实现,仅仅只是在tryAcquireShared实现上有略微不同。
    先来看看非公平类NonfairSync实现。tryAcquireShared调用的是Sync类的nonfairTryAcquireShared方法,方法的实现相当简单,仅仅是在循环内推断当前锁状态值减去请求值acquires后,假设remaining < 0(则表示此次acquire失败,直接返回负值remaining就可以)或者remaining >=0 时,compareAndSetState成功(表示此次acquire成功,直接返回大于等于0的remaining就可以),假设CAS失败,则继续循环重试,直到当中一种情况发生。
    再来看看公平类FairSync的实现。tryAcquireShared直接被重写,与非公平类版本号对照,添加了hasQueuedPredecessors的推断,该方法在AQS中表示是否有结点在当前的等待队列前排在自己前面,假设返回true,则表示当前线程须要进入等待队列,直接返回-1表示acquire失败。
    tryReleaseShared的实现也非常easy,也是一个循环里不断CAS把锁状态添加请求的releases就可以。
    Semaphore还有其他一些辅助方法,事实上现也都是简单地调用内部类Sync的方法,这里便不再赘述。

总结
    整体来看,AQS的锁状态值就等于Semaphore的许可量,acquire的实现就是把当前锁状态值,也就是许可量减去相应值,release的实现就是把锁状态值添加相应值就可以。整个实现结构和ReentrantLock类似,但没有了重入的逻辑,并且实现更是相对简单,理解起来应该没有难度。
资源下载链接为: https://pan.quark.cn/s/d9ef5828b597 在本文中,我们将探讨如何通过 Vue.js 实现一个带有动画效果的“回到顶部”功能。Vue.js 是一款用于构建用户界面的流行 JavaScript 框架,其组件化和响应式设计让实现这种交互功能变得十分便捷。 首先,我们来分析 HTML 代码。在这个示例中,存在一个 ID 为 back-to-top 的 div 元素,其中包含两个 span 标签,分别显示“回到”和“顶部”文字。该 div 元素绑定了 Vue.js 的 @click 事件处理器 backToTop,用于处理点击事件,同时还绑定了 v-show 指令来控制按钮的显示与隐藏。v-cloak 指令的作用是在 Vue 实例渲染完成之前隐藏该元素,避免出现闪烁现象。 CSS 部分(backTop.css)主要负责样式设计。它首先清除了一些默认的边距和填充,对 html 和 body 进行了全屏布局,并设置了相对定位。.back-to-top 类则定义了“回到顶部”按钮的样式,包括其位置、圆角、阴影、填充以及悬停时背景颜色的变化。此外,与 v-cloak 相关的 CSS 确保在 Vue 实例加载过程中隐藏该元素。每个 .page 类代表一个页面,每个页面的高度设置为 400px,用于模拟多页面的滚动效果。 接下来是 JavaScript 部分(backTop.js)。在这里,我们创建了一个 Vue 实例。实例的 el 属性指定 Vue 将挂载到的 DOM 元素(#back-to-top)。data 对象中包含三个属性:backTopShow 用于控制按钮的显示状态;backTopAllow 用于防止用户快速连续点击;backSeconds 定义了回到顶部所需的时间;showPx 则规定了滚动多少像素后显示“回到顶部”按钮。 在 V
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值