AQS深入分析

1.什么是AQS
在 Lock 中,用到了一个同步队列 AQS,全称:AbstractQueuedSynchronizer
它能够实现线程的阻塞以及唤醒,但它并不具备业务功能。
它是一个同步工具也是 Lock 用来实现线程同步的核心组件。

2.AQS 的两种功能 
从使用层面来说,AQS 的功能分为两种:独占和共享
独占锁:每次只能有一个线程持有锁,ReentrantLock 就是以独占方式实现的互斥锁;
共享锁:允许多个线程同时获取锁,并发访问共享资源,比如 ReentrantReadWriteLock

3.AQS 的内部实现
AQS 队列内部维护的是一个 FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。
所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。
每个 Node 其实是由线程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;
当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。

4.释放锁以及添加线程对于队列的变化
(1).释放锁
A.修改 head 节点指向下一个获得锁的节点;
B.新的获得锁的节点,将 prev 的指针指向 null;
设置 head 节点不需要用 CAS,原因是设置 head 节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要 CAS 保证,只需要把 head 节点设置为原首节点的后继节点,并且断开原 head 节点的 next 引用即可。
(2).添加线程到队列
A.新的线程封装成 Node 节点追加到同步队列中,设置 prev 节点以及修改当前节点的前置节点的 next 节点指向自己;
B.通过 CAS(compareAndSetState) 将 tail 重新指向新的尾部节点;(这里需要CAS来保证,因为加入队列的可能有多个线程)

5.CAS 的实现原理

/**
 * Atomically sets synchronization state to the given updated
 * value if the current state value equals the expected value.
 * This operation has memory semantics of a {@code volatile} read
 * and write.
 *
 * @param expect the expected value
 * @param update the new value
 * @return {@code true} if successful. False return indicates that the actual
 *         value was not equal to the expected value.
 */
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

通过 cas 乐观锁的方式来做比较并替换,如果当前内存中的 state 的值和预期值 expect 相等,则替换为 update。更新成功返回 true,否则返回 false.
这个操作是原子的,不会出现线程安全问题,这里面涉及到Unsafe这个类的操作,以及涉及到 state 这个属性的意义。
state 是 AQS 中的一个属性,它在不同的实现中所表达的含义不一样,对于重入锁的实现来说,表示一个同步状态
ReentrantLock 允许重入,所以同一个线程多次获得同步锁的时候,state 会递增(初始为0,表示无锁);
比如重入 5 次,那么 state=5。 而在释放锁的时候,同样需要释放 5 次直到 state=0 其他线程才有资格获得锁。

6.Unsafe 类
Unsafe 类是在 sun.misc 包下,不属于 Java 标准。
但是很多 Java 的基础类库,包括一些被广泛使用的高性能开发库都是基于 Unsafe 类开发的,比如 Netty、Hadoop、Kafka 等;
Unsafe 可认为是 Java 中留下的后门,提供了一些低层次操作,如直接内存访问、线程的挂起和恢复、CAS、线程同步、内存屏障。

7.AQS.accquire

/**
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

1).通过 tryAcquire 尝试获取独占锁,如果成功返回 true,失败返回 false;
(2).如果 tryAcquire 失败,则会通过 addWaiter 方法将当前线程封装成 Node 添加到 AQS 队列尾部;
(3).acquireQueued,将 Node 作为参数,通过自旋去尝试获取锁

8.NonfairSync.tryAcquire(java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire)
这个方法的作用是尝试获取锁,如果成功返回 true,不成功返回 false,它是重写 AQS 类中的 tryAcquire 方法。

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

AQS 中 tryAcquire方法的定义,并没有实现,而是抛出异常。
一般情况,一个不实现的模版方法,那应该定义成 abstract,让子类来实现,为什么这里的定义里面就是抛异常?

这样的设计,可能本意是这个方法不需要子类来实现,如果子类要实现也是可以的。

9.ReentrantLock.nofairTryAcquire

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();// 获取当前执行的线程
    int c = getState(); // 获得 state 的值
    if (c == 0) { // 表示无锁状态
        if (compareAndSetState(0, acquires)) { // 用cas替换state的值,cas 成功表示获取锁成功
            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;
}

10.AQS.addWaiter

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);// 把当前线程封装为 Node
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail; // tail 是 AQS 中表示同比队列队尾的属性,默认是 null
    if (pred != null) { // tail 不为空的情况下,说明队列中存在节点
        node.prev = pred; // 把当前线程的 Node 的 prev 指向 tail
        if (compareAndSetTail(pred, node)) { // 通过 cas 把 node加入到 AQS 队列,也就是设置为 tail
            pred.next = node; //设置成功以后,把原 tail 节点的 next 指向当前 node
            return node;
        }
    }
    enq(node); //tail=null,把 node 添加到同步队列
    return node;
}

enq 就是通过自旋操作把当前节点加入到队列中

/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
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;
            }
        }
    }
}

11.AQS.acquireQueued
通过 addWaiter 方法把线程添加到链表后,会接着把 Node 作为参数传递给 acquireQueued 方法,去竞争锁。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus; //前置节点的 waitStatus
    if (ws == Node.SIGNAL) //如果前置节点为 SIGNAL,意味着只需要等待其他前置节点的线程被释放
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true; //返回 true,意味着可以直接放心的挂起了
    if (ws > 0) { // ws 大于 0,意味着 prev 节点取消了排队,直接移除这个节点就行
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev; //相当于: pred=pred.prev;node.prev=pred; // 这样操作就删除了中间的一个节点,让其从链表中断开
        } while (pred.waitStatus > 0);//这里采用循环,从双向列表中移除 CANCELLED 的节点
        pred.next = node;
    } else { // 利用 cas 设置 prev 节点的状态为 SIGNAL(-1)
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

13.parkAndCheckInterrupt

/**
 * Convenience method to park and then check if interrupted
 *
 * @return {@code true} if interrupted
 */
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

使用 LockSupport.park 挂起当前线程变成 WATING 状态;
Thread.interrupted,返回当前线程是否被其他线程触发过中断请求,也就是 thread.interrupt(); 
如果有触发过中断请求,那么这个方法会返回当前的中断标识 true,并且对中断标识进行复位标识已经响应过了中断请求。
如果返回 true,意味着在 acquire 方法中会执行 selfInterrupt()。

14.LockSupport
LockSupport类是Java6引入的一个类,提供了基本的线程同步原语。
LockSupport实际上是调用了 Unsafe 类里的函数,归结到 Unsafe 里,只有两个函数:

unpark 函数为线程提供“许可(permit)”,线程调用 park 函数则等待“许可”。
permit 相当于 0/1 的开关,默认是 0,调用一次 unpark 就加 1 变成了 1.调用一次 park 会消费 permit,又会变成 0。 
如果再调用一次 park 会阻塞,因为 permit 已经是 0 了。直到 permit 变成 1.这时调用 unpark 会把 permit 设置为 1.
每个线程都有一个相关的 permit,permit 最多只有一个,重复调用 unpark 不会累积

15.锁的释放流程
ReentrantLock.unlock

/**
 * Releases in exclusive mode.  Implemented by unblocking one or
 * more threads if {@link #tryRelease} returns true.
 * This method can be used to implement method {@link Lock#unlock}.
 *
 * @param arg the release argument.  This value is conveyed to
 *        {@link #tryRelease} but is otherwise uninterpreted and
 *        can represent anything you like.
 * @return the value returned from {@link #tryRelease}
 */
public final boolean release(int arg) {
    if (tryRelease(arg)) { //释放锁成功
        Node h = head; //得到 aqs 中 head 节点
        if (h != null && h.waitStatus != 0) //如果 head 节点不为空并且状态!=0.调用 unparkSuccessor(h)唤醒后续节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

16.ReentrantLock.tryRelease

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

在排它锁中,加锁的时候状态会增加 1,在解锁的时候减掉 1,同一个锁,在可以重入后,可能会被叠加为 2、3、4 这些值;
只有 unlock() 的次数与 lock()的次数对应才会将 Owner 线程设置为空,而且也只有这种情况下才会返回 true。

17.unparkSuccessor

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus; //获得 head 节点的状态
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0); // 设置 head 节点状态为 0

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next; //得到 head 节点的下一个节点
    if (s == null || s.waitStatus > 0) {
        // 如果下一个节点为 null 或者 status>0 表示 cancelled 状态.
	 //通过从尾部节点开始扫描,找到距离 head 最近的一个 waitStatus<=0 的节点
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null) //next 节点不为空,直接唤醒这个线程即可
        LockSupport.unpark(s.thread);
}

18.为什么在释放锁的时候是从 tail 进行扫描
如果从前往后遍历,存在这样的情况:有线程正在调用 unlock 方法,由于 t.next=node 还没执行意味着链表的关系还没有建立完整。
就会导致遍历到 t 节点的时候被中断。所以从后往前遍历,一定不会存在这个问题。

19.原本挂起的线程继续执行
通过 ReentrantLock.unlock,原本挂起的线程被唤醒以后继续执行。
原来被挂起的线程是在 acquireQueued 方法中,所以被唤醒以后继续从这个方法开始执行。
由于 ThreadB 的 prev 节点指向的是 head,并且 ThreadA 已经释放了锁。所以这个时候调用 tryAcquire 方法时,可以顺利获取到锁。
(1). 设置新 head 节点的 prev=null;
(2). 设置原 head 节点的 next 节点为 null;

20.公平锁和非公平锁的区别
锁的公平性是相对于获取锁的顺序而言的,如果是一个公平锁,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是 FIFO。
非公平锁在抢占锁的时候,直接通过cas操作去尝试抢占,而公平锁会先判断自己是否有前驱,如果有则不能进行锁的抢占。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值