AQS之semaphore源码分析

  • 对于semaphore源码关注点在哪里
    1. semaphore加锁解锁的逻辑实现。
    2. 获取锁的线程释放锁唤醒阻塞线程竞争锁的逻辑。

  • 代码准备
public class SemaphoreTest1 {
    public static void main(String[] args) {
        Semaphore windows = new Semaphore(3);
        for(int i=0;i<5;i++){
            new Thread(()->{
                try {
                    windows.acquire();
                    System.out.println(Thread.currentThread().getName()+":开始买票");
                    Thread.sleep(5000);
                    System.out.println(Thread.currentThread().getName()+":购票成功");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    windows.release();
                }
            }).start();
        }
    }
}

        一个简单的买票逻辑模拟。我们还是按照分析reentrantlock的debug方式来分析代码。

  • debug执行过程

        我们还是首先控制thread0进入上锁acquire()方法,传入的arg参数为1,进入tryAcquireShared(arg)方法

         进入nonfairTryAcquireShared(int acquires)方法,此时,AQS中控制状态的state属性就是我们创建semaphore时初始化的数值(在非共享锁中,我们知道AQS的state为0时就需要阻塞线程了),最后返回remaining为2。

        这样的话,我们控制前三个线程依次进入上锁代码块都畅通无阻,直到我们第四个线程thread3. 

 
 

         在nonfairTryAcquireShared(int acquires)方法中,remaining为-1,后面的cas操作不会执行,available保持0。返回结果-1。这样的话acquireSharedInterruptibly()方法中进入第二个if判断的逻辑执行doAcquireSharedInterruptibly(arg)方法。

        可以看到这个方法的结构我们非常熟悉,和reentraintlock非常类似: addWaiter(Node.SHARED)方法是构建链表并入队;然后是一个for循环;for循环中先是获取当前节点的前一节点方法node.predecessor();接着进入一次获取锁尝试;然后if的逻辑内是出队部分内容;最后是改变node节点waitStatus属性的shouldParkAfterFailedAcquire()方法和阻塞线程的parkAndCheckInterrupt()方法。

        在组队和入队方法 addWaiter中就是AQS公用的方法,和reentrantlock相比就是我们的节点多了nextWaiter属性,此属性的作用会在后面提到。

        在一次尝试获取锁,此时state为0,所以返回的remaining为-1,所以跳过第一个if判断逻辑,我们进入shouldParkAfterFailedAcquire方法。

        可以看到此方法为AQS共用方法,和reentrantlock一致,也是将头节点的waitStatus属性由0改为SINGNAL(-1) ,最后会再次循环进入阻塞方法。

        我们重新来控制我们的thread0来释放锁。 

         我们可以看到tryReleaseShared()方法就是将我们锁状态state通过cas操作由0变为1.

         在doReleaseShared()方法中,将头结点的waitStatus状态又-1改为0,然后进入unparkSuccessor(h)方法唤醒线程。

        thread3被唤醒,remaining返回0,进入if判断内,执行setHeadAndPropagate()方法

        setHead() 方法为半出队方法,配合阻塞队列方法所在循环的后续方法实现节点出队。要注意后面的关于propagate关于是否传播的一系列操作。我们知道在构建SHARED属性的node节点时,我们添加了nextWaiter属性,通过这一属性让我们进入了doReleaseShared()方法中。

        此时我们的head节点为原先绑定thread3的节点,进入唤醒方法unparkSuccessor(h)

        我们会发现会唤醒thread3所在节点的下一节点,所以在每次获取锁时,所有节点为共享的被阻塞的线程都会被唤醒一次,若果后续节点的pred正好是头结点就会尝试进行获取锁,提高效率。 

        所以对于共享锁来说,只要资源数充足,就可以一直进行唤醒。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值