12、AQS(AbstractQueuedSynchronizer)详解

目录

1、前置知识要求

2、AQS入门级别理论知识

2.1 是什么

2.2 AQS为什么是JUC框架基础

2.3 锁和同步器的关系?

2.4 AQS可以干什么

3、AQS源码分析前置知识

3.1 AQS内部体系架构

3.2 AQS自身

3.3 AQS内部Node节点

4、AQS源码深度讲解和分析(ReentrantLock()为例)

4.1 ReentrantLock公平锁与非公平锁

4.1.1 非公平与公平锁lock()方法的区别

4.2 AQS acquire(int arg)方法三大流程走向

5、流程源码解析(非公平锁为例子-以例子流程解读代码)

5.0 第一个顾客抢占——lock()方法[A顾客]

5.1 第二个顾客抢占——lock()方法[B顾客]

5.2 实现类的(ReentrantLock) tryAcquire(int acquires)方法[B顾客]

5.3 AQS addWaiter(Node)方法[B顾客]

5.4 实现类的(ReentrantLock) tryAcquire(int acquires)方法[C顾客]

5.5 AQS addWaiter(Node)方法[C顾客]

5.6 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法[B顾客]

5.7 parkAndCheckInterrupt() 方法[B顾客]

5.8 [C顾客]和[B顾客]从5.6~5.8一致

5.9 [A顾客]释放锁——unlock()方法

5.10 cancelAcquire(node) 取消


1、前置知识要求

1、公平锁和非公平锁

2、可重入锁

3、自旋思想

4、LockSupport

5、数据结构之双向链表

6、设计模式之模板设计模式

2、AQS入门级别理论知识

2.1 是什么

        抽象的队列同步器

        是用来实现锁或者其它同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给谁的问题

        整体就是一个抽象的FIFO(first-in-first-out)队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态。

2.2 AQS为什么是JUC框架基础

以下类都依赖于AQS框架

2.3 锁和同步器的关系?

        锁面向的是使用者(程序员)。

        同步器,面向锁的实现者:Java并发大神DougLee,提出统一规范并简化了锁的实现,将其抽象出来,屏蔽了同步状态管理,同步队列的管理和维护,阻塞线程排队和通知、唤醒机制等;是一切锁和同步组件实现的——公共基础部分。

2.4 AQS可以干什么

        抢到资源的线程直接使用处理业务,抢不到资源的必然设计一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。

        如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的节点对象(Node),通过CAS、自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。

3、AQS源码分析前置知识

3.1 AQS内部体系架构

3.2 AQS自身

3.3 AQS内部Node节点

static final class Node {
    /** 共享模式 */
    static final Node SHARED = new Node();
    /** 独占模式 */
    static final Node EXCLUSIVE = null;

    /** 线程被取消 */
    static final int CANCELLED =  1;
    /** 后继线程需要被唤醒 */
    static final int SIGNAL    = -1;
    /** 等待condition唤醒 */
    static final int CONDITION = -2;
    /**
     * 共享式同步状态获取将会无条件的传播下去
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

    /**
     * 线程等待状态:初始值为0。其他值为上述几种状态
     */
    volatile int waitStatus;

    /**
     * 前置节点
     */
    volatile Node prev;

    /**
     * 后置节点
     */
    volatile Node next;

    /**
     * 当前线程
     */
    volatile Thread thread;

    /**
     * 表示该节点模式(是独占还是共享)
     */
    Node nextWaiter;

    /**
     * 是否是共享模式
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 返回前置节点
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

4、AQS源码深度讲解和分析(ReentrantLock()为例)

4.1 ReentrantLock公平锁与非公平锁

4.1.1 非公平与公平锁lock()方法的区别

都调用了acquire(1)方法

        我们发现该方法时AQS中的方法,但是AQS中的tryAcquire方法是一个没有具体实现的方法,需要子类自己去实现(这个也就是设计模式中的模板设计模式)

唯一区别公平锁判断是否有排队线程

         对比公平锁和非公平锁的tryAcquire()方法的实现代码,其实差别就在于非公平锁获取时比公平锁中少了一个判断!hasQueuedPredecessors()

hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:

        公平锁公平锁先来先得,线程获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;

        非公平锁不管是否有等待队列,都先抢占一下锁,如果获取到锁,则立刻占有锁对象;如果没有抢占到锁,则回到队尾排队。也就是说队列的第一个排队线程苏醒后,不一定就是派头线程获得锁,它还需要参加锁的竞争。

 

4.2 AQS acquire(int arg)方法三大流程走向

5、流程源码解析(非公平锁为例子-以例子流程解读代码)

        例如A、B、C三个顾客,去银行办理业务,A先到,此时窗口空无一人。

public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();

    // A严重耗时,长期占有窗口
    new Thread(() -> {
        lock.lock();
        try {
            System.out.println("----come in A");
            try {
                TimeUnit.MINUTES.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            lock.unlock();
        }
    },"A").start();

    // B顾客是第二个顾客,A占有窗口,去等候区等待,进入AQS队列
    new Thread(() -> {
        lock.lock();
        try {
            System.out.println("----come in B");
        }finally {
            lock.unlock();
        }
    },"B").start();
    // C顾客是第三个顾客,A占有窗口,去等候区等待,进入AQS队列
    new Thread(() -> {
        lock.lock();
        try {
            System.out.println("----come in C");
        }finally {
            lock.unlock();
        }
    },"C").start();
}

本来AQS原始状态

5.0 第一个顾客抢占——lock()方法[A顾客]

final void lock() {
    // 抢占公共资源state,试图将状态0改为1
    if (compareAndSetState(0, 1))
        // 如果抢占成功,将占有线程设置为当前线程(A顾客线程)
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

5.1 第二个顾客抢占——lock()方法[B顾客]

final void lock() {
    // 顾客B尝试获取锁,肯定失败,因为顾客A再占有
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 顾客B进入该方法
        acquire(1);
}

找到子类实现的方法

5.2 实现类的(ReentrantLock) tryAcquire(int acquires)方法[B顾客]

final boolean nonfairTryAcquire(int acquires) {
    // 得到顾客B线程
    final Thread current = Thread.currentThread();
    // 现在资源类中的state值为1
    int c = getState();
    // 如果state状态位为0的话,下面会尝试获取锁;但是目前B线程是不符合该条件
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 当前线程是否等于当前持有资源线程(这里就是可重入锁的实现情况)
    // 但是当前线程为A顾客,所以说不等于B顾客
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // B线程尝试抢锁所败
    return false;
}
public final void acquire(int arg) {
    // 上步tryAcquire(arg)返回false 取反之后继续下一步操作:addWaiter(Node.EXCLUSIVE)
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

5.3 AQS addWaiter(Node)方法[B顾客]

为当前线程和给定模式创建并排队节点

/**
 * 为当前线程和给定模式创建并排队节点
 * 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) {
    // 将B顾客封装撑node节点
    Node node = new Node(Thread.currentThread(), mode);
    // 获取到尾指针
    Node pred = tail;
    // 尾指针等于null,这里不会进入到if中(别着急,后面C顾客会走到)
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 进入该方法
    enq(node);
    return node;
}

AQS中的方法 enq(final Node node):将节点插入队列

private Node enq(final Node node) {
    // 第一次循环(不考虑CAS失败问题)
    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;
            }
        }
    }
}

第二次循环

private Node enq(final Node node) {
    // 第二次循环
    for (;;) {
        // 获取尾节点(上图可以看出是上次循环新建出来的虚拟节点)
        Node t = tail;
        // 进入不到该if中,将进入else
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 将当前B顾客的前置节点设置成上次尾节点的对象
            node.prev = t;
            // 将尾节点设置成当前节点
            if (compareAndSetTail(t, node)) {
                // 将老的尾节点的后置节点设置成B顾客节点
                t.next = node;
                return t;
            }
        }
    }
}

5.4 实现类的(ReentrantLock) tryAcquire(int acquires)方法[C顾客]

final boolean nonfairTryAcquire(int acquires) {
    // 得到顾客C线程
    final Thread current = Thread.currentThread();
    // 现在资源类中的state值为1
    int c = getState();
    // 如果state状态位为0的话,下面会尝试获取锁;但是目前C线程是不符合该条件
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 当前线程是否等于当前持有资源线程(这里就是可重入锁的实现情况)
    // 但是当前线程为A顾客,所以说不等于C顾客
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // C线程尝试抢锁所败
    return false;
}
public final void acquire(int arg) {
    // 上步tryAcquire(arg)返回false 取反之后继续下一步操作:addWaiter(Node.EXCLUSIVE)
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

5.5 AQS addWaiter(Node)方法[C顾客]

/**
 * 为当前线程和给定模式创建并排队节点
 * 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) {
    // 将C顾客封装撑node节点
    Node node = new Node(Thread.currentThread(), mode);
    // 获取到尾指针
    Node pred = tail;
    // 当前尾指针不等于null(上次B客户已经进入)
    if (pred != null) {
        // 与enq(node)方法中的else中的代码思想一致,只不过没有循环
        // 这里只不过是尝试设尾节点,如果设置成功,则不进入enq(node)方法;如果没有成功则进入
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果上面设置尾节点没有成功,则进入该方法
    enq(node);
    return node;
}

5.6 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法[B顾客]

// node 是B顾客节点
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // b顾客的前置节点是头节点
            final Node p = node.predecessor();
            // 判断p是否是头节点(true) 
            // 再尝试获取一下锁,这里我们给定的业务是A顾客长时间持有锁,所有tryAcquire(arg)=false
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 进入到 shouldParkAfterFailedAcquire(p, node) -- 跳到下个方法块
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
// pred 就是上面代码块中传过来的头节点,node就是上面代码块传过来的b节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取前置节点
    // 头节点的waitStatus == 0
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } 
    // 进入到这个方法
    else {
        // 将0改成-1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    // 返回该值
    /*
    * 所以 shouldParkAfterFailedAcquire(p, node) = false 不进入下一个方法中
    * 上个方法块重新自旋进入该方法后,在第一个if (ws == Node.SIGNAL)中返回true
    * 会进入到parkAndCheckInterrupt()方法
    * acquireQueued(final Node node, int arg){
    *    ....
    * if (shouldParkAfterFailedAcquire(p, node) &&
    *           parkAndCheckInterrupt())
    *}
    *
    */
    return false;
}

5.7 parkAndCheckInterrupt() 方法[B顾客]

// B顾客进入该方法
private final boolean parkAndCheckInterrupt() {
    // B顾客线程挂起
    LockSupport.park(this);
    return Thread.interrupted();
}

5.8 [C顾客]和[B顾客]从5.6~5.8一致

        将C顾客的前置结点状态设置为-1,然后将线程挂起

5.9 [A顾客]释放锁——unlock()方法

// ReentrantLock
public void unlock() {
    sync.release(1);
}

// AQS
public final boolean release(int arg) {
    // 进入到tryRelease(arg)方法
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// AQS 靠子类实现
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// ReentrantLock
// 尝试释放锁 releases == 1
protected final boolean tryRelease(int releases) {
    // getState() = 1 - releases = 0;c = 0
    int c = getState() - releases;
    // 如果当前要释放锁的线程 不等于持有锁的线程则抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    
    boolean free = false;
    if (c == 0) {
        free = true;
        // 将当前持有锁的线程设置成null
        setExclusiveOwnerThread(null);
    }
    // 将state修改为0
    setState(c);
    return free;
}

// AQS类中
public final boolean release(int arg) {
    // 上述返回true
    if (tryRelease(arg)) {
        // 获取头节点
        Node h = head;
        // 头节点不等于null 状态也不等于 0;进入if中的unparkSuccessor(h)方法
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
// AQS类中
// node = 头节点
private void unparkSuccessor(Node node) {
    // 头节点的状态是-1
    int ws = node.waitStatus;
    // 进入if
    if (ws < 0)
        // 将头节点waitStatus值重新设置为0
        compareAndSetWaitStatus(node, ws, 0);
    // 头节点的下一个节点为B节点
    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;
    }
    // B节点不等于null 
    if (s != null)
        // 唤醒B节点
        LockSupport.unpark(s.thread);
}

回到5.7代码块中

// B顾客进入该方法
private final boolean parkAndCheckInterrupt() {
    // B顾客线程挂起
    LockSupport.park(this);
    // A线程释放锁唤醒B线程,Thread.interrupted() 返回false
    return Thread.interrupted();
}

再回到5.6代码块中

// node 是B顾客节点
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // ...
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 上个代码块中返回false,再一次自旋for循环
                parkAndCheckInterrupt()) 
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

// ---------------------再一次自旋--------------
// node 是B顾客节点
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // b顾客的前置节点是头节点
            final Node p = node.predecessor();
            // 判断p是否是头节点(true) 
            // 再尝试获取一下锁(因为非公平锁会有其他刚抢占资源);B抢占锁,执行下面代码块
            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)
            cancelAcquire(node);
    }
}
final boolean nonfairTryAcquire(int acquires) {
    // 得到顾客B线程
    final Thread current = Thread.currentThread();
    // 现在资源类中的state值为0
    int c = getState();
    // 进入到if语句中
    if (c == 0) {
        // 将state从0改为1
        if (compareAndSetState(0, acquires)) {
            // 将持有锁线程设置为B顾客
            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;
}

// 继续上面返回的代码
// 再尝试获取一下锁(因为非公平锁会有其他刚抢占资源);B抢占锁,执行下面代码块
            if (p == head && tryAcquire(arg)) {
                // 将B顾客设置为头节点
                setHead(node);
                // 然后将p(展位头节点)的后置节点设置为null等待被回收
                p.next = null; // help GC
                failed = false;
                // 返回false,表示没有被中断;原来顾客B节点是新的占位节点
                return interrupted;
            }

5.10 cancelAcquire(node) 取消

private void cancelAcquire(Node node) {
    // 判断节点是否存在
    if (node == null)
        return;
    // 将等待线程设置为null
    node.thread = null;

    // 当前节点的前置节点
    Node pred = node.prev;
    // 跳过连续取消节点,寻找有效前置节点
    while (pred.waitStatus > 0)
        // 下面这句话这句话可以写成两句来理解
        // pred = pred.prev; 将pred设置为前当前节点的前置节点的前置节点
        // node.prev = pred; 将当前节点的前置节点设置成 前当前节点的前置节点的前置节点
        node.prev = pred = pred.prev;

    // 前置节点的下一个节点
    Node predNext = pred.next;

    // 将当前节点设置成取消状态
    node.waitStatus = Node.CANCELLED;

    // 如果当前节点是尾节点
    // compareAndSetTail(node, pred) 将尾节点设置成有效前置节点
    if (node == tail && compareAndSetTail(node, pred)) {
        // 将当前节点的前置有效节点的后置节点设置为null
        compareAndSetNext(pred, predNext, null);
    } else {
        // 中间节点取消
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            // 当前节点的后置节点
            Node next = node.next;
            // 当前节点的后置节点不为空,并且状态不是取消状态
            if (next != null && next.waitStatus <= 0)
                // 将当前节点的有效前置节点的后置节点设置成当前节点的后置节点
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郭吱吱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值