AbstractQueuedSynchronizer(队列同步器,AQS)源码剖析(三)

1.1release

        接下来我们在看看如何释放锁,源码如下

public final boolean release(int arg) {//释放锁方法(独占模式)
    if (tryRelease(arg)) {//尝试释放锁
        Node h = head;
        if (h != null && h.waitStatus != 0)//如果head结点不为空并且等待状态不等于0就去唤醒后继结点
            unparkSuccessor(h);//唤醒等待队列的下一个节点
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//获取state值,释放给定的量
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//如果c=0,表示锁已经释放
        free = true;
        setExclusiveOwnerThread(null);//表示当前没有线程占用锁
    }
    setState(c);//如果c不等于0,表示锁还没有完全释放,设置状态
    return free;
}
private void unparkSuccessor(Node node) {//唤醒后继节点
 
    int ws = node.waitStatus;//获取给定节点的等待状态
    if (ws < 0)//如果等待状态小于0,则置为0
        compareAndSetWaitStatus(node, ws, 0);

 
    Node s = node.next;//获取给定节点的下一个节点
    if (s == null || s.waitStatus > 0) {//如果后继节点为null或者,等待状态>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);//唤醒
}

        因此释放锁的过程可以总结为首先利用tryRelease尝试释放锁,如果失败,则返回false,否则调用unparkSuccessor方法去唤醒后继节点,成功则返回true。

1.2共享模式下获取锁

        在前文中,我们讲了如果在独占模式获取锁和释放锁,接下来我们在看看在共享模式下如何获取锁和释放锁,在共享模式下获取锁的方式也是三种,分别是:不响应线程中断获取acquireShared,响应线程中断获取acquireSharedInterruptibly,设置超时时间获取tryAcquireSharedNanos。

1.2.1不响应线程中断获取

        源码如下

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)//尝试获取锁
        doAcquireShared(arg);//获取失败进入该方法
}
//尝试获取锁,由子类实现。1.负数表示获取失败;2.0表示获取成功,后继节点无法在获取;3.正数表示获取成功,
//后继节点也可以获取成功
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

        当获取锁失败之后,会调用doAcquireShared方法将当前线程构建成共享模式插入队列尾部,源码如下

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) {//如果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);
    }
}
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    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();//唤醒后继节点
    }
}

        从上述代码可以看出,如果当前节点获取到锁,并且值大于0,则表示后面的节点也会获取到锁,因此当前节点就需要去唤醒后面同样是共享模式的结点,即使当前节点的后继节点为null,也会将等待状态设置为PROPAGATE来告诉后来的线程这个锁是可获取状态。

1.2.2响应线程中断获取

        源码如下

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();//判断线程是否中断,是抛出异常
    if (tryAcquireShared(arg) < 0)//尝试获取锁
        doAcquireSharedInterruptibly(arg);//获取失败进入该方法
}
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();//线程在阻塞过程中收到中断请求,立马抛出异常
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

1.2.3设置超时间获取

        源码如下

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();//响应中断
    return tryAcquireShared(arg) >= 0 ||
        doAcquireSharedNanos(arg, nanosTimeout);//尝试获取锁,失败则调用doAcquireSharedNanos方法
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    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);
    }
}

        如果第一次获取锁失败会调用doAcquireSharedNanos方法并传入超时时间,进入方法后会根据情况再次去获取锁,如果再次获取失败就要考虑将线程挂起了。这时会判断超时时间是否大于自旋时间,如果是的话就会将线程挂起一段时间,否则就继续尝试获取,每次获取锁之后都会将超时时间减去获取锁的时间,一直这样循环直到超时时间用尽,如果还没有获取到锁的话就会结束获取并返回获取失败标识。在整个期间线程是响应线程中断的。

1.3共享模式下释放锁

        接下来我们再看看共享模式下是如何释放锁的,源码如下

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {//尝试释放锁
        doReleaseShared();//成功则唤醒其他线程
        return true;
    }
    return false;
}
private void doReleaseShared() {
 
    for (;;) {
        Node h = head;//获取队列头节点
        if (h != null && h != tail) {
            int ws = h.waitStatus;//获取头节点的等待状态
            if (ws == Node.SIGNAL) {//如果head结点的状态为SIGNAL, 表明后面有人在排队
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);//唤醒后继结点
            }
            //如果head结点的状态为0, 表明此时后面没人在排队, 就只是将head状态修改为PROPAGATE
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

        首先调用tryReleaseShared方法尝试释放锁,该方法的判断逻辑由子类实现。如果释放成功就调用doReleaseShared方法去唤醒后继结点。另外如果没有没有后继节点,那么将状态设置为PROPAGATE。

猜你喜欢

转载自my.oschina.net/u/3475585/blog/1819107