从ReentrantLock看AQS

AQS的三个核心点

  • state
  • 协作类实现的获取锁/释放锁的方法
  • FIFO队列

关于state

state是用来判断是否有线程占用当前锁,与另一个参数exclusiveOwnerThread 配合使用

以ReentrantLock获取锁为例

/**
 * ReentrantLock 获取非公平锁的代码
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        // 设置state -》从0到1
        if (compareAndSetState(0, 1))
            // 将占用的线程改成当前线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 设置失败,说明当前的锁被其他线程占用,尝试获取锁
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

关于协作类实现的释放锁/获取锁的方法

以上面的例子为例,当线程要获取的锁被其他线程占用的时候,就需要我们去自定一个获取锁的逻辑

public final void acquire(int arg) {
    // tryAcquire 就是协作类自定义的获取锁的逻辑
    if (!tryAcquire(arg) &&
        // 获取失败,统一交给AQS管理(添加等待节点,放入队列中,将当前线程挂起)-这套属于固有的逻辑,不需要协作类去实现(实现成本高,且属于重复代码)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

那么来重点看看 tryAcquire 方法 (接着以非公平锁为例)

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // 再次获取一下state
    int c = getState();
    if (c == 0) {
        // 说明锁被释放,再次cas设置state
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果持有锁的线程是当前线索,state+1(可重入)
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

再次尝试,获取的到锁还好说。假如获取不到锁,就需要用到了刚刚提到的FIFO队列

AQS核心内容 FIFO队列及入队出队规则-入队

此处结合着我们刚刚将的流程来,不单独针对各个点做叙述

单独再将这块代码拿出来

if (!tryAcquire(arg) &&
    // 获取失败,统一交给AQS管理(添加等待节点,放入队列中,将当前线程挂起)-这套属于固有的逻辑,不需要协作类去实现(实现成本高,且属于重复代码)
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();

添加等待节点

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;
        // CAS将当前节点设置为尾节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 入队(此处是没有等待节点的情况)
    enq(node);
    return node;
}

// CAS操作
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

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;
            // 再将我们具体的节点入队,并将具体的节点设置为尾节点
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

尝试让队列中的头节点获取锁

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 获取当前节点的前置节点
            final Node p = node.predecessor();
            // 前置节点是head,说明当前节点是第一个等待锁的节点(此时也会让当前节点再次去尝试获取锁 即tryAcquire方法)
            if (p == head && tryAcquire(arg)) {
                // 获取锁成功的处理
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 获取锁失败的处理 shouldParkAfterFailedAcquire (这个方法一会单独讲)
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 挂起当前线程
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        // 响应中断
        if (failed)
            // 撤销尝试获取锁(这个也一会在再讲)
            cancelAcquire(node);
    }
}

是否要挂起当获取锁失败方法解析 shouldParkAfterFailedAcquire

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
      // 前缀节点的等待状态
      int ws = pred.waitStatus;
      if (ws == Node.SIGNAL)
          /*
           * This node has already set status asking a release
           * to signal it, so it can safely park.
           */
          // 前面有节点已经做好被唤醒的准备,就等资源释放,去获取锁,当前节点可以被挂起
          return true;
      if (ws > 0) {
          /*
           * Predecessor was cancelled. Skip over predecessors and
           * indicate retry.
           */
          // 说明是个从尾到头的查找过程
          do {
              node.prev = pred = pred.prev;
          // >0 说明前置节点已经被取消,可以直接删除
          } while (pred.waitStatus > 0);
          pred.next = node;
      } else {
          /*
           * waitStatus must be 0 or PROPAGATE.  Indicate that we
           * need a signal, but don't park yet.  Caller will need to
           * retry to make sure it cannot acquire before parking.
           */
          // 将前置节点设置为SIGNAL的状态,先入队的线程先被唤醒
          compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
      }
      return false;
}

此处放一个WaitStatus枚举的状态表

/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED =  1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL    = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
 * waitStatus value to indicate the next acquireShared should
 * unconditionally propagate
 */
static final int PROPAGATE = -3;

释放锁的代码

public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    // 尝试释放锁
    if (tryRelease(arg)) {
        Node h = head;
        // 如果有等待节点,尝试唤醒(即使创建的临时节点,也会在实际入队的过程中将临时节点改成SIGNAL状态)
        if (h != null && h.waitStatus != 0)
            // 唤醒
            unparkSuccessor(h);
        return true;
    }
    return false;
}

tryRelease 释放锁

protected final boolean tryRelease(int releases) {
    // state - release
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // state = 0,代表释放锁
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

AQS核心内容 FIFO队列及入队出队规则-出队

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        // 头节点<0,设置为初始状态 (小于0可能是非虚拟节点作为头节点的情况)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 从后往前找,找到最前面的可以被唤醒的节点(此处不做无效节点的删除,删除的操作在入队的时候做)
        // 猜测:此处也是从后往前找的原因估计是为了不和入队时候删除的代码冲突
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        // 唤醒节点,再走入刚刚的acquireQueued方法
        LockSupport.unpark(s.thread);
}

撤销等待节点 cancelAcquire

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // Skip cancelled predecessors
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        // 删除前面取消的节点
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    if (node == tail && compareAndSetTail(node, pred)) {
        // 尾节点,直接删除当前
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            // 找到前一个被唤醒的节点
            Node next = node.next;
            // 如果当前节点的下一个节点是符合被唤醒的条件,
            // 将前一个符合被唤醒的节点的下一个节点设置为当前节点的下一个节点
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

一个问题

关于刚刚为什么要从尾节点往前找去添加节点

node.prev = pred; compareAndSetTail(pred, node) 这两个地方可以看作Tail入队的原子操作,但是此时pred.next = node;还没执行,如果这个时候执行了unparkSuccessor方法,就没办法从前往后找了,所以需要从后往前找。还有一点原因,在产生CANCELLED状态节点的时候,先断开的是Next指针,Prev指针并未断开,因此也是必须要从后往前遍历才能够遍历完全部的Node。

流程图

获取锁的流程图

获取锁的流程图

释放锁的流程图

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

推荐阅读更多精彩内容