通常使用 ReentrantLock.tryLock 的时候,都会带上一个时间戳,如果到了时间仍然没获取锁返回 false。

不带时间戳,当前线程只会尝试获取一次锁,然后返回结果;带上时间戳,则当前线程在等待时间内会多次尝试获取锁。

这里面细节还挺多,在等待时间内,线程是否会挂起?

如果挂起,是怎么挂起的?线程如何醒来?

如果不挂起,又怎么去做?

static final long spinForTimeoutThreshold = 1000L;

// java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireNanos
/**
 * Acquires in exclusive timed mode.
 *
 * @param arg the acquire argument
 * @param nanosTimeout max wait time
 * @return {@code true} if acquired
 */
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    // 计算到期时间戳
    final long deadline = System.nanoTime() + nanosTimeout;
    // 先把节点加入到等待队列的尾部,等待队列是一个带头节点的双向链表
    // node 是当前节点,node.thread 是 currentThread
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            // 如果 node 的前驱节点是头节点,则尝试修改 state 值,获取锁
            if (p == head && tryAcquire(arg)) {
                // 这种情况只发生在队列中只有一个等待节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            // 判断是否到期
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                // 到期返回 false,先执行 finally 代码块
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                // 如果 nanosTimeout 大于自旋的时间,则直接挂起线程
                // 在 nanosTimeout 时间后,线程自动醒来,从此处继续执行 for 循环
                // 如果 nanosTimeout 小于自旋的时间,则当前线程一直自旋,执行 for 循环,直到获取锁,或者过期返回 false
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        // 线程最终获取锁失败,把节点从等待队列中移除
        if (failed)
            cancelAcquire(node);
    }
}

 

posted on 2020-06-20 12:28  偶尔发呆  阅读(1080)  评论(0编辑  收藏  举报