AQS源码总结

本文参考文章:万字超强图文讲解AQS以及ReentrantLock应用

1. CLH队列的头结点和尾结点都是虚拟节点,头结点可以理解为哨兵节点。并且为了节省内存都是lazy initialized. 并且头结点如果存在,可以保证部位CANCELLED状态

2. 在doAcquireNanos(int args, long nanosTimeout)方法中,也就是具有超时功能的获取锁的过程中,并不是一开始就park住线程的,有一个自旋阈值,如果超过了在挂起,因为如果时间没有超过1000 nanosseconds的话没必要进行挂起和唤醒操作,不如直接让线程进入下一次循环。这个思想跟JDK中的自旋锁很像,自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间很短。自旋等待不能替代阻塞,虽然他可以避免线程切换带来的开销,但是它占用CPU处理器的时间,如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好。

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);
        }
    }
  /**
     * The number of nanoseconds for which it is faster to spin
     * rather than to use timed park. A rough estimate suffices
     * to improve responsiveness with very short timeouts.
     */
    static final long spinForTimeoutThreshold = 1000L;

3. AQS中的conditionObject类,里面有两个成员变量,firstWaiter, lastWaiter。 在await方法中会释放同步状态,所以说condition.await会释放锁,但是LockSupport.park并不会释放锁,类似于Sleep一样,不会释放锁,只会响应中断。

 public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

 addWaiter方法是构建一个自己的同步队列,这个队列是单向队列,为什么是单向的,是因为这里不涉及到竞争锁,只是做一个条件等待队列。在Lock中定义多个条件,每个条件都会对应一个条件等待队列。

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

关于wait的使用是有范式的,必须在lock.lock中使用,类似于wait的使用要在synchronized中一样,具体原因可以查看文章synchronized底层原理。从条件等待队列移到同步队列是有时间差的,有时间差就会有公平和非公平的问题。

公平锁就是判断队列是否还有前驱节点存在,如果没有才能获取锁,而非公平锁不管这个事,直接去获取。 

4.  AQS里面用到了模板方法的设计模式,一种经典的设计模式。把关于CLH队列的操作封装起来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值