带你看看Java-AQS同步器 源码解读<三>共享锁

  1. Java-AQS同步器 源码解读<一>独占锁加锁
  2. Java-AQS同步器 源码解读<二>独占锁解锁
  3. Java-AQS同步器 源码解读<三>共享锁
  4. Java-AQS同步器 源码解读<四>-条件队列上
  5. Java-AQS同步器 源码解读<五>-条件队列下

共享锁

前面2篇文章描述了AQS中独占锁的加锁解锁,那今篇文章我们聊下AQS 中分享锁的加锁解锁
既然说道共享锁和独占锁,那2者最本质的区别是什么呢,大家应该记得AQS中有一个同步器状态State 字段,其实说说白了共享模式和独占模式,就是同步器的状态是否允许被多个线程所获取,比如我们之前说的ReentrantLock就是独占锁的模式,因为同步器状态只能被一个线程所获取,那这篇我将使用Semaphore来做分析共享锁。

共享锁加锁

Semaphore初始化

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

上面是信号量的默认构造函数 默认实现的是非公平锁

  /**
     * NonFair version
    */
    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);
        }
    }

我们看到我们传递的信号量permits 最终还是调用了Sync的构造函数


    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

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

setState 其实就是调用AQS中的方法 就是给State 赋值

Semaphore获取 acquire() 方法

选取一个默认的获取方法如下:

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

可以看到 我们这边默认获取的信号量是1,当然acquire也有带参数的构造方法
很明显 我们看到acquire默认的构造函数调的是Sync中acquireSharedInterruptibly方法 之前我也说过Sync是继承了AQS的 我们IDE跟进这个方法 就进入了AQS类中

进入AQS中acquireSharedInterruptibly方法

/*这个方法就是去获取同步锁,除非线程发送了中断*/
 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())//这边就是坚持线程是否发生中断,如果中断则抛出异常
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

重写tryAcquireShared的实现

看了上面的代码,我们有看到一个熟悉的方法tryAcquireShared和之前独占锁tryAcquire很像,这个方法也是需要子类去重写的,熟悉的套路,熟悉的味道,哈哈!那我们就去找下tryAcquireShared这个方法
找下找 很快我找到了 在NonfairSync里面找到了 代码上面也有,NonfairSync非公平锁的tryAcquireShared方法原来是调用的父类的方法也就是Sync的,那我就去Sync类中看一看,果然找到了

 final int nonfairTryAcquireShared(int acquires) {
            for (;;) {//又是一个自旋的操作,AQS 中有大量的这样的写法
                int available = getState();
                int remaining = available - acquires;
                //整个自旋唯一的出口,就是当前的线程获占用完同步器的状态值后小于0或者CAS修改State值失败就返回了
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

看到这个返回我很懵逼呀,怎么就返回了呢,那我们结合刚才上面的acquireSharedInterruptibly来看 原来他判断如果返回值小于0 就会执行下面的方法doAcquireSharedInterruptibly方法,翻译成白话文 就是 就是 同步器里面5个苹果,可能前面几个线程都把苹果拿了 我再来拿的时候发现小于0了,拿怎么办呢 ,只能去排队等待咯,和独占锁流程差不多,区别就是这个后面,好的 我们慢慢再开看,

doAcquireSharedInterruptibly 获取失败后排队

/**
     * Acquires in shared interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
       //以共享模式加入到阻塞队列中 这里addWaiter和独占锁加锁使用的是同一个方法 不清楚的 可以看之前的文章
        final Node node = addWaiter(Node.SHARED);// 返回成功加入队尾的节点
        boolean failed = true;//标识是否获取资源失败
        try {
            for (;;) {//自旋
                final Node p = node.predecessor();// 获取当前节点的前置节点
                if (p == head) {// 如果前置节点是head 那就去尝试获取资源,因为可能head已经释放了资源
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {// 如果获取成功且大于等于0,意味这资源还有剩余,可唤醒其余线程获取
                        setHeadAndPropagate(node, r);// 这边方法就是和独占锁处理不一样地放 我们可以重点去看下 其余的流程是一样的
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
               /*下面的方法和独占锁的是一样的 在第一篇文章中已经解读过,小伙伴们如果不清楚 可以去看下 
               有区别的地方就是对中断的处理这边是直接抛出中断异常,独占锁处理是返回标记是否中断 让上一层处理中断
               */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

setHeadAndPropagate方法

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; //赋值当前的head节点 因为下一步会对head 重写赋值
        setHead(node);//设置当前node 节点 为head  这个和独占锁的是一样的
        /*
         * propagate > 0 的意思 就是同步器里面State是有剩余的 可以唤醒其他线程
         * 后面的判断意思是 当前的head节点或者之前的head节点等于null 或者状态小于0 那也必须能唤醒后面线程去获取资源 head等于null 说明可能被GC回收了 
       *  这边的head的waitStatus  我自己模拟了下各自情况 只可能是-1或者-3
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;//当前node 的下一个节点
            if (s == null || s.isShared())//如果snull
                doReleaseShared();
        }
    }

doReleaseShared方法

private void doReleaseShared() {
        /*
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {// 这个head!=tail 说明阻塞队列中至少2个节点 不然也没必要去传播唤醒 如果就自己一个节点 就算资源条件满足 还换个谁呢?
                int ws = h.waitStatus;// head 节点状态SIGNAL
                if (ws == Node.SIGNAL) {// 如果head状态是
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);//是和独占锁释放用的同样的方法 唤醒的是下一个节点 上一篇有分析到
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;          //这边设置为-3 是为了唤醒的传播 也就是满足上一个方法有判断waitStatus 小于0
            }
            if (h == head)   
                break;
        }
    }

这个条件是自旋唯一的出口就是head 没有发送变化 说明没有后面的线程获取资源 那就退出自旋,如果head发生了变化 说明传播的有效果了 后面线程获取到到了资源
还有要注意的地方 是doReleaseShared这个方法有2个地方调用 一个是就是这边共享锁加锁 还有一个就是共享锁解锁的地方

共享锁解锁

我们看下共享锁的解锁 其实看完了上面的内容 这个就简单了很多

Semaphore获取 releaseShared() 方法

代码在Semaphore类中
  public void release() {
        sync.releaseShared(1);
    }
  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))//CAS 更新同步器状态值 成功就退出自旋
                    return true;
            }
        }
代码跳转到了AQS类中
 public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {//tryReleaseShared 还是和之前的套路一样 子类去重写的 也很简单 代码也贴在了上面
            doReleaseShared();//是不是很熟悉
            return true;
        }
        return false;
    }

看到doReleaseShared 我们应该很熟悉了 刚才加锁的时候 也用到了这个方法 具体就不多说了

总结共享锁和独占锁 区别之处

看了共享锁的加锁 我们在回顾下独占锁加锁 这边的处理,不然想到 这边的区别就是head获取资源后 独占锁直接设置自己为head 然后返回 而共享锁这边head 获取资源后 如果资源状态还有剩余 就会唤醒其余线程去获取,这就是2者的区别
同样的解锁的过程也是几乎一样 底层唤醒线程的unparkSuccessor方法都是公用的,解锁的过程也是多一个唤醒传播的过程

好的AQS的同步队列 的共享模式和独占模式 用了前面的3篇文章 和大家分享完了
后面 会分析下AQS中的条件队列 具体怎么运行的~
不要吝啬你的点赞 ,点赞 给我东西 ,继续写作

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容