Java并发编程专题之AQS

主要参考了博客JUC框架 源码解析系列文章目录 JDK8

AbstractQueuedSynchronizer

概述

实现大量依赖乐观锁的方式(即CAS+自旋)。它实现了一个FIFO的等待队列用于等待获取同步状态,而获取/释放同步器状态的函数则依靠子类来实现。

虽然AQS是一个抽象类,但却没有任何抽象方法。如果定义为抽象方法确实不合适,因为继承使用AQS并不一定需要使用到AQS提供的所有功能(独占锁和共享锁),这样子类反而需要实现所有抽象方法。如果定义为空实现的普通方法,虽然不需要子类实现所有空方法了,但这样还是不够明确。现在AQS将这些方法的实现为抛出UnsupportedOperationException异常,那么如果是子类需要使用的方法,就覆盖掉它;如果是子类不需要使用的方法,一旦调用就会抛出异常。

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

简单使用案例(实现一个共享锁):

自定义同步器实现时主要实现以下几种方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
public class MyLock implements Lock {
    private final Sync sync = new Sync(2);

    private static final class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) {
            if (count <= 0) throw new IllegalArgumentException();
            setState(count);
        }

        @Override
        protected int tryAcquireShared(int acquireCount) {
            while (true) {
                int cur = getState();
                int newCount = cur - acquireCount;
                if (newCount < 0 || compareAndSetState(cur, newCount)) {
                    return newCount;
                }
            }
        }

        @Override
        protected boolean tryReleaseShared(int releaseCount) {
            while (true) {
                int cur = getState();
                int newCount = cur + releaseCount;
                if (compareAndSetState(cur, newCount)) {
                    return true;
                }
            }
        }
    }

    @Override
    public void lock() {
        sync.acquireShared(1);
    }

    @Override
    public void unlock() {
        sync.releaseShared(1);
    }
}

重要属性

state

  • state用volatile修饰,保证了它的可见性。
  • 如果是多线程并发修改的话,采用compareAndSetState来操作state
  • 如果是在没有线程安全的环境下对state操作(例如ReentrantLock释放锁,因为它之前已经获取到独占锁,所以没必要用CAS),采用setState方法
private volatile int state;

protected final int getState() {
    return state;
}

protected final void setState(int newState) {
    state = newState;
}

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

等待队列

AQS中已经为我们实现了一个FIFO的等待队列,它是一个双向链表。由于同步器的state一般不能让所有线程同时获得,所以将这些需要暂时等待的线程包装成一个节点放到队列中去,当获取state的条件满足时,会将这个节点内的线程唤醒,以便它接下来去尝试获取state。

static final class Node {
    static final Node SHARED = new Node(); 
    static final Node EXCLUSIVE = null; 

    static final int CANCELLED = 1;
    static final int SIGNAL = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3; 

    volatile int waitStatus; // 表明node代表线程的状态
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;

    Node nextWaiter; // 表明当前node的线程是想要获取共享锁还是独占锁

    final boolean isShared() {
        return nextWaiter == SHARED;
    }
}
private transient volatile Node head; // 固定是一个dummy node,因为它的thread成员固定为null
private transient volatile Node tail; // 请求锁失败的线程,会包装成node,放到队尾
  • head节点中thread成员为null,可以理解为将它的thread成员放到AQS的exclusiveOwnerThread属性上
  • 即使等待线程只有一个,等待队列中的节点个数也肯定是2个,因为第一个节点总是dummy node。

acquire(int arg)

流程

20216141
  • 首先调用子类的tryAcquire尝试获取独占锁一次,try的意思就是只试一次,要么成功,要么失败。
  • 获取不到则调用addWaiter(Node.EXCLUSIVE)将该线程加入等待队列的尾部,并标记为独占模式
  • acquireQueued使线程在等待队列中获取资源,中途可能不断经历阻塞/唤醒状态,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
  • 如果线程在等待过程中被中断过,它是不响应的。但是当acquireQueued返回真时,代表这期间函数曾经检测到过中断状态,并且将中断状态消耗掉了(Thread.interrupted()),所以需要在退出acquire之前,将中断状态重新设置上。
/**
Acquires in exclusive mode, ignoring interrupts.
Implemented by invoking at least once tryAcquire, returning on success.
Otherwise the thread is queued, 
possibly repeatedly blocking and unblocking, invoking tryAcquire until success.
*/
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
/**
该方法的默认实现是抛出UnsupportedOperationException,具体实现由自定义的扩展了AQS的同步类来实现。
AQS在这里只负责定义了一个公共的方法框架。
没有定义成abstract,是因为独占模式下只用实现tryAcquire-tryRelease,
而共享模式下只用实现tryAcquireShared-tryReleaseShared。
如果都定义成abstract,那么每个模式也要去实现另一模式下的接口。
*/
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

addWaiter(Node)

202161413
  • 将当前线程封装成一个节点(Node.EXCLUSIVE互斥模式、Node.SHARED共享模式)
  • 尝试快速入队:通过一次CAS加入到等待队列的队尾。
  • 如果CAS失败或者队列为空,则通过enq(node)方法初始化一个等待队列
  • 在enq(node)中,如果队列为空,则会给头部设置一个空节点:compareAndSetHead(new Node()),随后不断自旋直到把node加入到等待队列队尾。这个循环只有在compareAndSetTail(t, node)成功时才会退出循环,这就保证了enq最终肯定能将参数node放到队尾。就算只有一个线程入队,入队完毕后队列将有两个node,第一个node称为dummy node,因为它的thread成员为null;第二个node才算是实际意义的队头,它的thread成员不为null。新建的是空node,它的所有成员都是默认值。thread成员为null,waitStatus为0。之后你会发现,队尾node的waitStatus总是0,因为默认初始化。
  • 返回当前线程所在的结点

注意点

如果是多线程执行,可能导致多个node.prev链接到了tail,但是通过CAS保证tail.next只会链接到其中一个Node,并且其他的Node在不断的自旋中最终还是会加入到等待队列中

prev的有效性:有可能产生这样一种中间状态,即node.prev指向了原先的tail,但是tail.next还没来得及指向node。这时如果另一个线程通过next指针遍历队列,就会漏掉最后一个node。但是如果是通过tail.prev来遍历等待队列,就不会漏掉节点

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { 
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

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

acquireQueued

202161412
  • 每次循环都会判断是否可以尝试获取锁(判断前驱节点p是否为head),如果可以,那么尝试tryAcquire(arg)
  • 如果不可以尝试,或者获取锁失败,则通过parkAndCheckInterrupt阻塞线程并检查线程中断状态
  • 如果线程被unpark/interrupt,则会从park中返回,接着从parkAndCheckInterrupt()返回,继续往下执行
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; 
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed) cancelAcquire(node); // 该方法不会被执行
    }
}

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

shouldParkAfterFailedAcquire

Node的状态

static final int CANCELLED =  1; 
static final int SIGNAL    = -1; 
static final int CONDITION = -2;
static final int PROPAGATE = -3;
  • CANCELLED代表线程已经取消等待

  • SIGNAL说明这个node的后继node的代表线程已经阻塞或马上阻塞。当前node成为head并释放锁时,会根据SIGNAL来唤醒后继node。即SIGNAL是唤醒后继节点的标志。

  • 一个node新建的时候,它的waitStatus是默认初始化为0

说明

获取锁失败了才会执行该函数:

  1. p == head为false,即当前线程的node的前驱不是head
  2. 虽然 p == head为true,虽然当前线程虽然已经排到等待队列的最前面,但获取锁还是失败了

只有当该函数返回true时,才会去执行parkAndCheckInterrupt

作用:跳过无效前驱,把node的有效前驱(有效是指node不是CANCELLED的)找到,并且将有效前驱的状态设置为SIGNAL,之后便返回true代表马上可以阻塞了。给前一个节点设置SIGNAL,相当于一个闹钟,当前一个节点释放锁时,唤醒当前节点

执行两次
如果刚开始前驱的状态为0,那么需要第一次执行compareAndSetWaitStatus(pred, ws, Node.SIGNAL)返回false并进入下一次循环,第二次才能进入if (ws == Node.SIGNAL)分支,所以说至少执行两次。死循环保证了最终一定能设置前驱为SIGNAL成功的。(考虑当前线程一直不能获取到锁)

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL) return true;
    if (ws > 0) {
        do {
            // 是CANCELLED,说明前驱节点已经因为超时或响应了中断,而取消了自己,需要跳过他们
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 把一个node的状态变成SIGNAL
    }
    return false;
}

parkAndCheckInterrupt

调用完LockSupport.park(this),当前线程就阻塞在这里,直到有别的线程unpark了当前线程,或者中断了当前线程。而返回的Thread.interrupted()代表当前线程在阻塞的过程中,有没有被别的线程中断过,如果有,则返回true。注意,Thread.interrupted()会消耗掉中断的状态,即第一次执行能返回true,但紧接着第二次执行就只会返回false了。

如果是别的线程unpark了当前线程,那么调用Thread.interrupted()返回false。
如果是别的线程中断了当前线程,那么调用Thread.interrupted()返回true。

回到acquireQueued的逻辑中,发现一旦当前线程被中断过一次,那么parkAndCheckInterrupt就返回了true,那么执行interrupted = trueinterrupted局部变量就一定是true的了。(该中断状态会永久保留,用于最外层acquire中恢复用户中断)

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

注意点

忽略中断

整个过程忽略用户发出的中断信号(也就是由于线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出),直到acquireQueued执行结束后,才通过selfInterrupt恢复用户的中断

为什么tryAcquire(arg)的前提是p == head?

从enq的逻辑可知,head只会是一个dummy node,实际意义的node只会在head的后面。而node的前驱是head(final Node p = node.predecessor()),则代表node已经是队列中的第一个实际node了,排在最前面的node自然可以尝试去获取锁了。

回想整个调用过程,是最开始在acquire里调用tryAcquire就已经失败了,然而此时第一次循环时,又可能马上去调tryAcquire(说可能,是因为需要p == head成立),这会不会是一次肯定失败的tryAcquire?
考虑这种场景,线程1获取了锁正在使用还没释放,此时队列为空,线程2此时也来获取锁,自然最开始在acquire里调用tryAcquire会失败,假设线程2刚开始执行acquireQueued,此时线程1释放了锁,此时线程2肯定排在head后面,那么线程2马上tryAcquire,然后就可以获取成功。

执行acquireQueued的线程是谁?
一定是node参数的thread成员,虽然执行过程中,可能会经历不断阻塞和被唤醒的过程。

为什么刚执行完addWaiter方法时,才把代表当前线程的node放到队尾,怎么之后一判断就会发现自己处于head的后继了?

考虑addWaiter时,队列中有许多node。这说明从head到当前方法栈中的node之间的那些node,它们自己也会在执行acquireQueued,它们依次执行成功(指p == head && tryAcquire(arg)成功),每次执行成功相当于把head成员从队列上后移一个node,当它们都执行完毕,当前方法栈中的node自然也就是head的后继了。
“之间的那些node”的最后一个node执行acquireQueued成功后(代表 最后一个node的代表线程获得锁成功,它自己成为了head),当前方法还在阻塞之中,只有当这“最后一个node”释放独占锁时,才会执行unparkSuccessor(head),当前线程才会被唤醒。

finally块是否会执行cancelAcquire(node)?

虽然号称此函数是不响应中断的函数,但不响应中断只是对于AQS的使用者来说,如果一个线程阻塞在parkAndCheckInterrupt这里,别的线程来中断它,它是会马上唤醒的,然后继续这个循环。不过想要退出这个函数,只有通过return interrupted,而前一句就是failed = false,所以finally块里,是永远不会去执行cancelAcquire(node)的。

release(int arg)

独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。

释放锁的过程,根本不会区分公平或不公平、响应中断或不响应中断、超时或不超时。这是因为,这些区别都只是存在于尝试获取锁的方式上而已,既然已经获得了锁,也就不需要有这些区别。

细节

  • 如果遇到s == null,说明我们遇到一种中间状态,next指针还没有指好。如果遇到s.waitStatus > 0,说明head后继刚取消了。这两种情况,都需要从队尾的prev往前找。
  • 注意循环条件t != null && t != node,它会从队尾一直往前找,直到t是null或t已经到达了node。一般情况下,不会出现t != null,所以,这样循环肯定能找到node之后第一个不是取消状态的节点。
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        // 如果从头到尾都只有一个线程在使用锁,那么队列也不会初始化,head肯定为null。
        // 当队列只有一个dummy node时,它的状态为0,也就不会执行unparkSuccessor(h)了
        // 当head的状态为SIGNAL时,说明head后继已经设置了闹钟,会执行unparkSuccessor(h)。
        if (h != null && h.waitStatus != 0) unparkSuccessor(h);
        return true;
    }
    return false;
}

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0) compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    /*
    * head后继一般能直接通过next找到,但只有prev是肯定有效的。
    * 所以遇到next为null,肯定需要从队尾的prev往前找。
    * 遇到next的状态为取消,也需要从队尾的prev往前找。
    */
    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) LockSupport.unpark(s.thread);
}

acquireInterruptibly(int arg)

进入这个方法后,会第一次进行tryAcquire尝试。但不同的,此acquireInterruptibly函数中,会去检测Thread.interrupted(),并抛出异常。

对于acquireInterruptibly这个方法而言,既可以是公平的,也可以是不公平的,这完全取决于tryAcquire的实现(即取决于ReentrantLock当初是怎么构造的)。

如果检测到中断信号,首先线程会从LockSupport.park()中返回,并且抛出InterruptedException异常,执行cancelAcquire方法,将该线程代表的节点从等待队列中移除,并根据情况选择是否unparkSuccessor后续节点

doAcquireInterruptibly不需要返回值,因为该函数中如果检测到了中断状态,就直接抛出异常就好了。

doAcquireInterruptibly方法的finally块是可能会执行到cancelAcquire(node)的,而acquireQueued方法不可能去执行cancelAcquire(node)的。在doAcquireInterruptibly方法中,如果线程阻塞在parkAndCheckInterrupt这里后,别的线程来中断阻塞线程,阻塞线程会被唤醒,然后抛出异常。本来抛出异常该函数就马上结束掉的,但由于有finally块,所以在结束掉之前会去执行finally块,并且由于failed为true,则会执行cancelAcquire(node)。

public final void acquireInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted()) throw new InterruptedException();
    if (!tryAcquire(arg)) doAcquireInterruptibly(arg);
}

private void doAcquireInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed) cancelAcquire(node);
    }
}
202162111
private void cancelAcquire(Node node) {
    if (node == null) return;
    node.thread = null;
    Node pred = node.prev;
    while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 跳过CANCELLED的节点

    // 执行完循环,pred会指向node的有效前驱
    Node predNext = pred.next;

    // 如果别的线程在执行这步之后,别的线程将会跳过这个node。
    // 如果别的线程在执行这步之前,别的线程还是会将这个node当作有效节点。
    node.waitStatus = Node.CANCELLED;

    // 如果node是队尾,直接设置pred为队尾,然后设置pred的后继为null
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        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
    }
}

tryAcquireNanos(int arg, long nanosTimeout)

tryAcquireNanos这个方法与不响应中断的acquire方法对应。同样的,进入这个方法后,会第一次进行tryAcquire尝试。但不同的,此tryAcquireNanos函数中,会先去检测Thread.interrupted(),并抛出异常

但注意,对于tryAcquireNanos这个方法而言,既可以是公平的,也可以是不公平的,这完全取决于tryAcquire的实现(即取决于ReentrantLock当初是怎么构造的)。

差别

每次循环都会检查时间是否到达deadline。
当剩余时间小于spinForTimeoutThreshold时,则不能调用LockSupport.parkNanos,因为时间太短,反而无法精确控制阻塞时间,所以不如在剩余的时间里一直循环。
LockSupport.parkNanos除了会因为别人的park而唤醒,也会因为别人的中断而唤醒,当然最重要的,时间到了,它自己会唤醒。
不管哪种情况,被唤醒后,都会检查中断状态。每个循环都会检查一次。

如果中断,也同样进入cancelAcquire方法

final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    if (Thread.interrupted()) throw new InterruptedException();
    return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}

private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    if (nanosTimeout <= 0L) return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L) return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted()) throw new InterruptedException();
        }
    } finally {
        if (failed) cancelAcquire(node);
    }
}

acquireShared

共享锁与独占锁的区别

  • 独占锁是线程独占的,同一时刻只有一个线程能拥有独占锁,AQS里将这个线程放置到exclusiveOwnerThread成员上去。
  • 共享锁是线程共享的,同一时刻能有多个线程拥有共享锁,但AQS里并没有用来存储获得共享锁的多个线程的成员。
  • 如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁。但独占锁不会这样做,因为锁是独占的。
  • 当然,如果一个线程刚释放了锁,不管是独占锁还是共享锁,都需要唤醒在后面等待的线程

流程

  • 创建的节点不同。共享锁使用addWaiter(Node.SHARED),所以会创建出想要获取共享锁的节点。而独占锁使用addWaiter(Node.EXCLUSIVE)。
  • 获取锁成功后的善后操作不同。共享锁使用setHeadAndPropagate(node, r),因为刚获取共享锁成功后,后面的线程也有可能成功获取,所以需要在一定条件唤醒head后继。而独占锁使用setHead(node)。
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0) doAcquireShared(arg);
}
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
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);
    }
}

/**
setHead函数只是将刚成为将成为head的节点变成一个dummy node。
而setHeadAndPropagate里也会调用setHead函数。
但是它在一定条件下还可能会调用doReleaseShared
“如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁”。
*/
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; 
    setHead(node);
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared()) doReleaseShared();
    }
}

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

/**
* 复原中断状态,虽然这个版本的函数不用响应中断。
* 当acquireQueued返回真时,代表这期间函数曾经检测到过中断状态,并且将中断状态消耗掉了
* 所以需要在退出acquire之前,将中断状态重新设置上。
*/
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

releaseShared(int arg)

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

protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}


// 在获取共享锁成功时,也可能会调用到doReleaseShared。
private void doReleaseShared() {
    for (; ; ) {
        Node h = head;
        // 如果队列从来没有初始化过(head为null),或者head就是tail,则直接判断head是否变化过。
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue;
                unparkSuccessor(h);
            } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
                continue;
            }
        }
        /*
        循环检测到head没有变化时就会退出循环
        head变化一定是因为acquire thread被唤醒,之后它成功获取锁,然后setHead设置了新head
        保证了只要在某个循环的过程中有线程刚获取了锁且设置了新head,就会再次循环
        目的当然是为了再次执行unparkSuccessor(h),即唤醒队列中第一个等待的线程
        */
        if (h == head) break;
    }
}

你可能感兴趣的:(Java并发编程专题之AQS)