2021-08-11_AQS6个核心操作图解总结

2021-08-11  本文已影响0人  kikop

20210811_AQS6个核心操作图解总结

1概述

1.1流程时序

抢锁-->抢锁失败入队-->Park阻塞-->放弃入队即出队-->任务完成,释放锁-->唤醒同步队列中的兄弟。

1.2核心6个操作

1.2.1抢锁

// 非公平锁
final void lock() {
            if (compareAndSetState(0, 1)) // 不管三七二十一,先获取一下锁
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1); // 回看公平锁
        }

// 公平锁    
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败,则以独占方式加入队列
            selfInterrupt();
    }

首先,看锁标志位state(默认0),T1线程抢到锁,则基于CAS机制修改标志位,设置独占线程,如下图:

image-20210811154659834.png

其次,又有线程T2来抢锁

  1. 是自己锁的线程T1,则是重入,计重入次数+1
  2. 是其他线程T2,抢锁失败,则进行入队,具体如1.2.2。
  3. 公平锁判断依据:hasQueuedPredecessors,看等待区有没有人

1.2.2入队

这里,waitStatus:我们陈之为闹钟。为唤醒使用,CLH算法(效率高,能充分利用cpu),默认0(初始状态)-->1(取消)-->-1(可以唤醒下一个了)

普通队列特点:线程1顺利抢到锁,第一个节点则为线程2节点。(第一个线程执行,第二个线程放入队列,所以队列中节点1对应线程1节点

AQS队列特点:第一个节点为空节点,第二个节点才为入队的首个线程2节点。

  1. 我们知道,AQS同步队列中目前没有节点,则要首先New一个新的节点,然后会初始化一个NULL空节点。这点一定要理解。
image-20210913214614548.png image-20210913214651339.png
  1. 队列中目前已有节点,则尾部插入。

  2. 中部插入(aqs不存在)

1.2.3阻塞(park)

image-20210913214840806.png

1.2.4释放锁

  1. 修改锁标志位state
  2. 从同步器头结点head开始,unparkSuccessor(head) ,h.waitStatus等待闹钟的状态变化-1(需要唤醒后继节点)-->0(回归初始状态)
  3. 找到唤醒节点s,LockSupport.unpark(s.thread); // 唤醒线程T2,通过步骤2和步骤3我们知道要对等待闹钟waitStatus进行2次操作
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
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;
        if (ws < 0) // 等待闹钟:-1-->0,然后释放
            compareAndSetWaitStatus(node, ws, 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;
        if (s == null || s.waitStatus > 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);
    }

1.2.5唤醒(T2线程)

//1.parkAndCheckInterrupt
if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
// 2.selfInterrupt(); os给唤醒的线程中断信号
// 3.T2线程继续执行自己的业务逻辑
image-20210913214923492.png

1.2.6出队(过号)

   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) // 这里,failed=true,why todo
                cancelAcquire(node);
        }
    }
上一篇下一篇

猜你喜欢

热点阅读