AQS介绍--2(Reentrantlock加锁过程)

47 阅读3分钟

lock()函数

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

在lock函数中,如果某一个线程通过CAS操作,实现了state从0变为1。表示成功加锁了,因此把持有锁的线程设置为当前线程,即exclusiveOwnerThread这个属性。
如果没有成功

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

当获取锁失败时就会进入acquire(1)方法,在这个方法内会再次尝试获取锁,进入tryAcquire(1)方法再次尝试获取锁

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
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;
}

在再次尝试获取锁的时候,如果加锁成功了会执行和lock函数中成功上锁一样的逻辑,如果发现持有锁的线程和当前线程一致,其实也是上锁成功,就是把status加一,表示锁重入。如果失败,就会进入acquireQueued方法,执行之前呢会执行addWaiter(Node.EXCLUSIVE)方法

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;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

在addWaiter方法中,先把获取锁失败的线程打包成了一个Node对象(假设这是第一个阻塞的线程)因此AQS的尾指针就是null,pred是null进入enq方法

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;
            }
        }
    }
}

下图给了一个操作过程图

aqs1.jpg 很简单,这个enq方法就是把阻塞的线程加入到一个对列里面。最后,这个addWaiter最终的作用是把阻塞的线程封装到Node对象里面,然后放到一个对列里面,并最后返回这个封装的Node对象。注意:在封装成Node的时候,waitStatus=0,相当于是Initalize状态

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);
    }
}

在上面的源码中可以看到,只有当这个阻塞的节点的前驱节点是头节点的时候,才会继续尝试获取锁。如果获取锁成功了,那么就会把当前的这个Node节点设置为头节点,并把对以前节点的引用移除方便GC。注意:这些线程到现在都没有进行阻塞动作。同时,只有当该节点的前驱节点,这个线程才会有机会再次尝试获得锁。
如果当前的这个没有获取到锁的线程封装的Node对象的前驱节点不是头节点的时候,就会依次执行 shouldParkAfterFailedAcquire方法和parkAndCheckInterrupt方法。

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;
}

为了方便说明,明确一下,在ReentrantLock锁 的lock()方法中,waitStatus仅仅只有0和SIGNAL这两个状态。
同时传入的两个参数分别是一个Node对象和他的前驱Node节点对象。我们发现其实在这个方法中,主要是把前驱节点的waitStatus设置为SIGNAL。因为如果这个函数返回为false,那就会重新进入acquireQueued这个方法的for循环,重新执行,并且这次执行一定是true。因此进入下一个方法parkAndCheckInterrupt。

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

从上面的函数看到,在这个方法中开始阻塞当前的线程。这才是真正使得线程进入阻塞状态的方法。