ReentrantLock 源码分析以及 AQS (一) (2)

我们看下公平锁的获取锁的方法:

final void lock() { //通过 CAS 操作把 state 设置为 1 if (compareAndSetState(0, 1)) //如果设值成功,说明加锁成功,保存当前获得锁的线程 setExclusiveOwnerThread(Thread.currentThread()); else //如果加锁失败,则执行 AQS 的acquire 方法 acquire(1); } public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

acquire

这个方法的逻辑是:

通过 tryAcquire 方法,尝试获取锁,如果成功,则返回 true,失败返回 false 。

tryAcquire 失败之后,会先调用 addWaiter 方法,把当前线程封装成 node 节点,加入同步队列(独占模式)。

acquireQueued 方法会把刚加入队列的 node 作为参数,通过自旋去获得锁。

tryAcquire

这是一个模板方法,具体的实现需要看它的子类,这里对应的就是 ReentrantLock.NonfairSync.tryAcquire 方法。我们看一下:

protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { //当前线程 final Thread current = Thread.currentThread(); //获取当前的同步状态,若为 0 ,表示无锁状态。若大于 0,表示已经有线程抢到了锁。 int c = getState(); if (c == 0) { //然后通过 CAS 操作把 state 的值改为 1。 if (compareAndSetState(0, acquires)) { // CAS 成功之后,保存当前获得锁的线程 setExclusiveOwnerThread(current); return true; } } // 如果 state 大于0,则判断当前线程是否是获得锁的线程,是的话,可重入。 else if (current == getExclusiveOwnerThread()) { //由于 ReentrantLock 是可重入的,所以每重入一次 state 就加 1 。 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }

addWaiter

如果获取锁失败之后,就会调用 addWaiter 方法,把当前线程加入同步队列。

private Node addWaiter(Node mode) { //把当前线程封装成 Node ,并且是独占模式 Node node = new Node(Thread.currentThread(), mode); //尝试快速入队,如果失败,则会调用 enq 入队方法。enq 会初始化队列。 Node pred = tail; //如果 tail 不为空,说明当前队列中已经有节点 if (pred != null) { //把当前 node 的 prev 指针指向 tail node.prev = pred; //通过 CAS 把 node 设置为 tail,即添加到队尾 if (compareAndSetTail(pred, node)) { //把旧的 tail 节点的 next 指针指向当前 node pred.next = node; return node; } } //当 tail 为空时,把 node 添加到队列,如果需要的话,先进行队列初始化 enq(node); //入队成功之后,返回当前 node return node; }

enq

通过自旋,把当前节点加入到队列中

private Node enq(final Node node) { for (;;) { Node t = tail; //如果 tail为空,说明队列未初始化 if (t == null) { //创建一个空节点,通过 CAS把它设置为头结点 if (compareAndSetHead(new Node())) //此时只有一个 head头节点,因此把 tail也指向它 tail = head; } else { //第二次自旋时,tail不为空,于是把当前节点的 prev指向 tail节点 node.prev = t; //通过 CAS把 tail节点设置为当前 node节点 if (compareAndSetTail(t, node)) { //把旧的 tail节点的 next指向当前 node t.next = node; return t; } } } }

acquireQueued

入队成功之后,就会调用 acquireQueued 方法自旋抢锁。

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)) { //如果抢锁成功,就把当前 node 设置为头结点 setHead(node); p.next = null; // help GC failed = false; //抢锁成功后,会把线程中断标志返回出去,终止for循环 return interrupted; } //如果抢锁失败,就根据前驱节点的 waitStatus 状态判断是否需要把当前线程挂起 if (shouldParkAfterFailedAcquire(p, node) && //线程被挂起时,判断是否被中断过 parkAndCheckInterrupt()) //注意此处,如果被线程被中断过,需要把中断标志重新设置一下 interrupted = true; } } finally { if (failed) //如果抛出异常,则取消锁的获取,进行出队操作 cancelAcquire(node); } }

setHead

通过代码,我们可以看到,当前的同步队列中,只有第二个节点才有资格抢锁。如果抢锁成功,则会把它设置为头结点。

private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }

需要注意的是,这个方法,会把头结点的线程设置为 null 。想一下,为什么?

因为,此时头结点的线程已经抢锁成功,需要出队了。自然的,队列中也就不应该存在这个线程了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsgyp.html