java并发之AbstractQueuedSynchronizer

引言

可能我们平常很少听说它,就算是在并发里也很少用到它,但是我们所用的Condition(条件)、ReentrantLock(可重入锁)、ReentrantReadWriteLock(读写锁)就是根据它所构建的,它叫做队列同步器,是构建其他锁和同步组件的基础框架。

同步器的主要使用方法是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了对同步状态进行更改,就需要使用同步器提供的3个最主要的方法

 protected final int getState() ; //获取同步状态
 protected final void setState(int newState) ;//设置同步状态
 protected final boolean compareAndSetState(int expect, int update) ;//原子性设置同步状态(使用CAS)

AQS本身是没有实现任何同步接口的,它仅仅是定义了若干同步状态的获取和释放来供自定义同步组件使用,它可以支持独占式的获取同步状态(只能有一个线程使用,其他都得等待),也支持共享式的获取同步状态(如读写锁中的读锁)

同步器的设计是采用的模板方法设计模式,我们只需要重写指定的方法,随后将同步器组合在自定义同步组件中,并调用模板方法提供的模板方法即可。

源码分析

AQS里面可以重写的主要方法如下(需要我们自己实现逻辑):

        protected boolean tryAcquire(int arg) ; //独占式的获取锁

        protected boolean tryRelease(int arg) ;//独占式的释放锁
       
        protected int tryAcquireShared(int arg) ;//共享式的获取锁

        protected boolean tryReleaseShared(int arg) ; //共享式的释放锁
      
        protected boolean isHeldExclusively() ; //判断当前线程是否是在独占模式下被线程占用,一般表示是否被当前线程所占用
       

AQS提供的主要模板方法如下(我们不能重写):

   public final void acquire(int arg) ; //独占式获取锁

   public final void acquireInterruptibly(int arg); //与acquire(int arg)一样,但响应中断

   public final boolean tryAcquireNanos(int arg, long nanosTimeout);//在acquireInterruptibly(int arg基础上加入了超时机制,在规定时间内没有获取到锁返回false
获取到了返回true

   public final boolean release(int arg) ;//独占式的释放同步状态

   public final void acquireShared(int arg) ; //共享式的获取锁

   public final void acquireSharedInterruptibly(int arg);  //在acquireShared(int arg)加入了对中断的响应

  public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)  //在acquireSharedInterruptibly(int arg)基础上加入超时机制

  public final boolean releaseShared(int arg) ; //共享式的释放锁

这上面的所有方法都加上了final,表明了它们不可继承,这就是模板方法设计模式

下面我们来看一下它的源码

  • 首先是独占式同步锁的获取
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这是独占式获取锁的入口

具体流程如下:
 1、用tryacquire(arg)去获取同步状态(需要我们自己实现)
 2、获取不到把线程封装Node节点(addwaiter),在加入等待队列中
  • tryAcquire(int arg)源码
protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

源码就是简单地抛出异常,所以需要我们自己去实现

  • addWaiter 源码
 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            //尝试加入到队尾,因为这可能有多个线程竞争,采用compareAndSetTail(CAS操作)
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
      //如果表头为空又或者节点没加入到队尾
        enq(node);
        return node;
    }

先构造成Node节点

  • Node是其内部类,主要构造如下
static final class Node {
  volatile int waitStatus;
  volatile Node prev;
  volatile Node next;
  volatile Thread thread;
  Node nextWaiter;
}
  • prev:前驱节点;

  • next:后继节点;

  • thread:进入队列的当前线程

  • nextWaiter:存储condition队列中的后继节点。

    • waitStatus:节点状态,主要有这几种状态:
      1、 CANCELLED:当前线程被取消;
      2、SIGNAL:当前节点的后继节点需要运行;
      3、 CONDITION:当前节点在等待condition
      4、PROPAGATE:当前场景下后续的acquireShared可以执行;

队列的基本结构如下:

3994601-525f0d33e1512800.png
  • enq源码
/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
     //死循环(自旋)地插入到队尾
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                //通过compareAndSetTail保证节点能被线程安全添加
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

设置尾节点的过程

3994601-f9f4486a56dc72f4.png

加入到同步队列中之后,就执行 acquireQueued

  • acquireQueued源码

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

节点进入同步队列后,就开始自旋,每个节点都在观察自己是否满足条件,当“条件”满足,就可以获取同步状态,然后从自旋地过程中退出

这里有两点要说明:
1、头节点是获取到同步状态的点,而头节点的线程释放同步状态后,会唤醒其后续节点,所以每个节点都在判断自己的前驱节点是否是头节点,是之后看能不能获取到同步状态,只有两者都满足时,才能退出
2、维护同步队列的是fifo原则(先进先出),整个过程如图所示:

3994601-032edf539ec5d54b.png

总结一下独占式获取锁的流程:

3994601-59833f358ae1cf04.png

当前线程获取到同步状态后,就需要释放同步状态,使后续节点能够获取,主要是通过release(int arg) 实现的

  • release(int arg) 源码
 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryrelease:判断能否释放同步状态
unparkSuccessor(h):唤醒后续节点

上面讲了独占式获取获取锁,下面讲讲共享式获取

共享式和独占式最大的区别是同一时刻能否有多个线程同时获取到同步状态,如图所示

3994601-af326de0d86eb2e9.png

共享式获取的入口是acquireShared 方法

  • acquireShared 源码
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

tryAcquireShared方法是需要我们自己实现的,返回的是int值,当返回值大于0时,表示能获取到同步状态

  • doAcquireShared源码
 private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

先把线程加入到同步队列中,然后死循环,判断前驱节点是否是头节点,然后获取同步状态,当两者都满足是就退出循环

与独占式获取同步状态一样,共享式获取也是需要释放同步状态的,AQS提供releaseShared(int arg)方法可以释放同步状态。

  public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

1、调用tryReleaseShared方法释放状态;
2、 调用doReleaseShared方法唤醒后继节点;

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

推荐阅读更多精彩内容