AQS详解,通过ReentrantLock窥探AQS。

AQS详解,通过ReentrantLock窥探AQS

      • AQS简介
      • 1. AQS成员介绍
        • 1.1 AbstractOwnableSynchronizer
        • 1.2 内部类AbstractQueuedSynchronizer.Node
        • 1.3 AQS的成员介绍
      • 2. 通过ReetrantLock来分析AQS
        • 2.1 ReentrantLock lock = new ReentrantLock();
        • 2.2 lock.lock();也就是ReentrantLock.NonfairSync.lock()
          • 2.2.1 NonfairSync.tryAcquire() -> Sync.nonfairTryAcquire()
          • 2.2.2 AQS.addWaiter(Node.EXCLUSIVE)
          • 2.2.3 AQS.acquireQueued()
        • 2.2 lock.unlock();-> AQS.release()
        • 3. 公平锁lock.lock();
        • 4. 总结

AQS简介

AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,如下图所示。AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量。

1. AQS成员介绍

1.1 AbstractOwnableSynchronizer

首先是AQS的父类AbstractOwnableSynchronizer,这个类很简单,就一个成员变量以及它的getter/setter方法,
exclusiveOwnerThread用来标记当前同步器被哪个线程占用。

private transient Thread exclusiveOwnerThread;// 独占同步模式的当前所有者。

1.2 内部类AbstractQueuedSynchronizer.Node

Node是一个FIFO的队列上的节点类,该队列是一个双向链表结构,Node使用成员变量prev和next来指向上一个和下一个Node节点。
Node的成员如下:

static final Node SHARED = new Node(); 表明Node正处于共享模式下等待。
static final Node EXCLUSIVE = null; 表明Node正处于独占模式下等待。
volatile Node prev; 前置Node节点。
volatile Node next; 后置Node节点。
volatile Thread thread; 该Node节点中包含的线程。
volatile int waitStatus; 当前节点的状态。他的值包括如下:
static final int CANCELLED =  1; 节点被取消。
static final int SIGNAL    = -1; 后置节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行;
static final int CONDITION = -2; 当前节点进入等待队列中
static final int PROPAGATE = -3; 表示下一次共享式同步状态获取将会无条件传播下去
static final int INITIAL = 0; 初始状态。
Node nextWaiter; 等待队列中的下一个节点,或者指向特定值上面的SHARED成员。
final boolean isShared() {return nextWaiter == SHARED;}; 返回true表示该Node处于共享模式下等待。印证了上面的特定值SHARED的含义。
final Node predecessor(); 返回前置Node节点。

1.3 AQS的成员介绍

private transient volatile Node head; 队列头部节点。
private transient volatile Node tail; 队列尾部节点。
private volatile int state; 当前同步状态。

2. 通过ReetrantLock来分析AQS

我们一般使用ReetrantLock的代码如下:

ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();

2.1 ReentrantLock lock = new ReentrantLock();

public ReentrantLock() {
   sync = new NonfairSync();
}

可以看到,空参的ReentrantLock默认是创建的非公平锁。职于公平锁和非公平锁的区别,我们会在后面分析完源码后得出结论。
sync是ReentrantLock的内部类,它继承自AQS,从这里可以看到ReentrantLock其实就是使用AQS来实现锁机制的。
Sync有两个实现类,FairSync和NonfairSync,根据ReentrantLock的构造参数来创建不同的Sync实现类。
这里我们先以默认实现非公平锁来分析。

2.2 lock.lock();也就是ReentrantLock.NonfairSync.lock()

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

该方法首先通过CAS设置当前AQS的state为1,如果设置成功,会将当前线程设置到AQS的独占模式所有者的成员中,也就是表明当前线程获取到了锁。
如果设置失败继续调用AQS的acquire(1)

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire()是一个抽象方法,需要AQS的子类来实现。

2.2.1 NonfairSync.tryAcquire() -> Sync.nonfairTryAcquire()
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            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;
        }

如果AQS当前status为0,则尝试通过CAS获取锁。如果state不为0,判断当前独占模式所有者是不是当前线程,如果是,重新设置state,一般就是加1,这也就是我们常说的可重入锁,同一个线程可以多次获取同一个锁。 如果当前独占不是当前线程,则返回false,表明尝试获取锁失败。

当tryAcquire()尝试获取锁失败,会继续调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

2.2.2 AQS.addWaiter(Node.EXCLUSIVE)
private Node addWaiter(Node mode) {
		//创建一个nextWaiter为Node.EXCLUSIVE,
		//thread为当前线程的Node节点。
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        //当AQS的尾部节点不为空,尝试通过CAS将上面的新节点设为队尾。
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //当AQS的尾部节点为空,或者上面通过CAS设置队尾失败的话,调用enq入队。
        enq(node);
        return node;
    }

AQS传入一个独占模式的Node.EXCLUSIVE给addWaiter()方法。我们再来看下enq()方法。

//enq()方法通过CAS自旋来入队。
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //当AQS的队尾为null,
                //创建一个新的空的Node来充当队首和队尾。
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            //通过CAS将Node入队。
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

通过上面可以看出,addWaiter()方法的作用就是将新的Node节点入队到AQS的队尾,如果是空队列,会创建一个空的Node来充当队首

2.2.3 AQS.acquireQueued()

上面addWaiter()方法将新的线程包装到Node中,并将该Node通过CAS加入的AQS的队尾并返回传递给acquireQueued()方法。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //如果新建的Node的prev前置节点是AQS的队首,
                //那么再次尝试获取锁。
                if (p == head && tryAcquire(arg)) {
                //如果获取成功,将该node设置成队首,
                //同时将原队首的next置为空,从而将原队首出队。
                //设置头的时候,会将node的thread和prev都设为空。
                //所以队首一般都相当于一个虚拟的Node。
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //否则会挂起当前线程。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

acquireQueued()方法会首先判断新建的Node的前置节点是否为AQS的队首,如果是,再次尝试tryAcquire()获取锁,如果成功,将当前新的Node设置为队首,并将原队首出队。
否则的话,会继续调用shouldParkAfterFailedAcquire()尝试挂起当前线程。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

shouldParkAfterFailedAcquire方法的作用通过源码可以知道,将新入队的Node的前置节点的waitStatus置为Node.SIGNAL同时需要注意的是,如果前置节点的状态是大于0,一般就是CANCELLED=1,说明前置节点被取消了。它会遍历该前置节点的前置节点,直到找到状态不是CANCELLED的,并将该不CANCELLED的Node设置为当前Node的前置节点。从而将中间那些CANCELLED的Node全部出队。

只有shouldParkAfterFailedAcquire()中pred本来就是SIGNAL状态的时候才会返回true,其他情况都是false。当返回true的时候继续调用parkAndCheckInterrupt(),使用LockSupport.park(this)将当前线程挂起。当shouldParkAfterFailedAcquire()返回false的时候,不会挂起线程,继续for循环自旋直到线程挂起或者Node的前置节点成为head并且tryAcquire成功。

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

parkAndCheckInterrupt()使用LockSupport.park()实现线程挂起。

如此,整个获取锁的过程就分析完了。ReentrantLock的非公平锁在调用lock()的时候就尝试获取锁,当获取失败就会加入到AQS的队列中被挂起,等待unpark()。

2.2 lock.unlock();-> AQS.release()

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

如果当前线程不是AQS独占模式的所有者线程,则抛出异常,否则将AQS的state-1。当state=0的设置释放锁,设置独占所有者为null。因为ReentrantLock是可重入锁,前面在lock的时候,如果相同线程多次加锁,会将state多次+1。所以在这里释放的时候也需要相同次数的unlock,将state减到0

当tryRelease()将state减为0,返回true。AQS的release()方法会继续调用unparkSuccessor(),启动队列中下一个节点的线程

lock.lockInterruptibly()。和lock()方法大致相同,只是多了可以抛出InterruptedException异常

3. 公平锁lock.lock();

公平锁就是在创建ReentrantLock的时候传入true。

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

公平锁的lock()方法直接调用的AQS的acquire()方法,所以和非公平锁就需要实现的抽象方法tryAcquire()不一样。下面看下公平锁的tryAcquire()。

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

hasQueuedPredecessors()判断队列中是否有除了head以外的有效Node节点。

    public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

4. 总结

从上面可以看到公平锁和非公平锁的区别:

  • 公平锁在tryAcquire()的时候,判断队列中是否还有Node等待执行,如果有,那么就去排队,没有才去竞争获取锁。
  • 非公平锁,在lock()和tryAcquire()的时候都会直接去竞争锁,不管AQS的队列中是否有其他的Node在排队。

ReentrantLock在lock的时候非公平锁会尝试竞争获取锁,其他的和公平锁基本一样,如果获取锁失败,他们就会入队到AQS的FIFO的队列当中,当前置节点释放锁后,非公平锁的后续节点会和刚刚创建还未入队的线程争抢锁,公平锁则直接按照队列顺序向后释放线程。
通过AQS的state是否等于0判断当前AQS同步器是否被占用,相同线程重复获取锁,state会依次逐渐+1,同理释放锁的时候依次-1,直到state再次等于0,表示锁被释放。
AQS通过LockSupport.park()和LockSupport.unpark()来挂起和释放线程,使用起来比较方便。

你可能感兴趣的:(javaweb)