23_AQS源码分析

前置知识:

  • 公平锁和非公平锁
  • 可重入锁
  • LockSupport
  • 自旋锁
  • 数据结构之链表
  • 设计模式之模板设计模式

是什么

字面意思:抽象的队列同步器

在这里插入图片描述

源代码:AbstractQueuedSynchronizer 简称为AQS

技术翻译:是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态。

这里的同步器组件指CountDownLatch、Semaphore、ReentrantLock、ReentrantReadWriteLock 等

在这里插入图片描述

锁和同步器的关系

锁:面向锁的使用者(定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可)

同步器:面向锁的实现者(比如Java并发大神Douglee,提出统一规 范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。)


能干嘛

加锁会导致阻塞,有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理

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

既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

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

在这里插入图片描述


AQS初识

官网解释:

有阻塞就需要排队,实现排队必然需要队列

AQS使用一个volatile的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成 一个 Node节点 来实现锁的分配,通过CAS完成对State值的修改。

在这里插入图片描述
在这里插入图片描述


AQS内部体系架构

在这里插入图片描述


AQS自身

在这里插入图片描述

AQS的int变量:

AQS的同步状态State成员变量

类比,银行办理业务的受理窗口状态:

  • 零就是没人,自由状态可以办理
  • 大于等于1,有人占用窗口,等着去
/**
* The synchronization state.
*/
private volatile int state;

AQS的CLH队列

CLH队列(三个大牛的名字组成),为一个双向队列

类比,银行侯客区的等待顾客

在这里插入图片描述


小总结:

  • 有阻塞就需要排队,实现排队必然需要队列
  • state变量+CLH双端Node队列

内部类Node(Node类在AQS类内部)

Node的int变量

Node的等待状态waitState成员变量(注意与status状态区分,status表示同步状态)

类比,等候区其它顾客(其它线程)的等待状态

​ 队列中每个排队的个体就是一个Node.

volatile int waitStatus;

Node此类的讲解

内部结构:

在这里插入图片描述


属性说明:

在这里插入图片描述


总结

在这里插入图片描述


从我们的ReentrantLock开始解读AQS

Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的

Lock lock = new ReentrantLock();

在这里插入图片描述

ReentrantLock原理

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

从最简单的lock方法开始看看公平和非公平

通过ReentrantLock的源码来讲解公平锁和非公平锁 可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()

hasQueuedPredecessors是公平锁加锁时判断等待队列中是否存在有效节点的方法

在这里插入图片描述

非公平锁走起,方法lock()

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

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

  • 公平锁: 公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;
  • 非公平锁: 不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程在unpark(), 之后还是需要竞争锁(存在线程竞争的情况下)
AQS源码深度分析走起

需要的快捷键:

  • ctrl + alt + B:跳到实现类
  • ctrl + alt + 左箭头/右箭头:回到上次操作/回到下一次操作

公平锁: 从lock()开始,依次跳…

public void lock() {
    sync.lock();
}

final void lock() {
    acquire(1);
}

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

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

protected final boolean tryAcquire(int acquires) {
    ....
}

非公平锁: 从lock()开始,依次跳…

public void lock() {
    sync.lock();
}

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

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

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    ....
}

无论是公平还是非公平,最终都会走向tryAcquire

在这里插入图片描述


带入一个银行办理业务的案例来模拟我们的AQS如何进行线程的管理和通知唤醒机制 :

3个线程模拟3个来银行网点,受理窗口办理业务的顾客

package com.aqs.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
     * @Author: xj0927
     * @Description:
     * @Date Created in 2021-01-25 16:43
     * @Modified By:
     */
public class AQSDemo {
    public static void main(String[] args) {
        
        //非公平锁
        ReentrantLock lock = new ReentrantLock();

        // A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("-----A thread come in");
                try {
                    TimeUnit.MINUTES.sleep(20);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } finally {
                lock.unlock();
            }
        }, "A").start();

        //第二个顾客,第二个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时B只能等待,
        // 进入候客区
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("-----B thread come in");
            } finally {
                lock.unlock();
            }
        }, "B").start();

        //第三个顾客,第三个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时C只能等待,
        // 进入候客区
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("-----C thread come in");
            } finally {
                lock.unlock();
            }
        }, "C").start();
    }
}

lock方法分析

A顾客过来办理业务,由于是非公平方式,走下面这条路

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

由于state的状态确实为0,和CAS预估值一样,CAS操作成功,将state值改为1,同时将Node节点中的线程设置为当前线程A

初始状态:
在这里插入图片描述

第一个顾客来之后:


acquire()方法分析

第二个顾客过来:

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

在这里插入图片描述

此时state的状态值为1,代表锁已经被占有,CAS失败,走acquire()方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

先走tryAcquire()方法,如果成功就返回ture,取反返回false,就不走后面的方法

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

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread(); //获取当前线程:此时是顾客B
    int c = getState(); //获取state状态,此时值为1
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            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;
}

下面对这个类中的两个判断进行分别解读:

if (c == 0) {
    if (compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
    }
}

情况1:顾客B此时走入大厅,发现柜台有人在办理业务,需要去候客区排队,刚准备坐下时,此时顾客A办理完成,就直接去窗口办理:判断当前state状态是否为0,如果为0,进行CAS操作,将state设置为1

else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0) // overflow
        throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
}
//附上state+1的方法
protected final void setState(int newState) {
    state = newState;
}

情况2:顾客A办理完,准备起身走时,发现还有件事忘记了办理,又坐下进行办理:判断当前线程是否为线程A,如果是,将state的状态值+1,(可重入锁的实现

总结:这两种情形都是可以获取到锁,即走tryAcquire()方法时返回true


addWaiter方法分析

但,此时两种情形都不满足:即返回false,取反为true,继续走后面的方法addWaiter(Node.EXCLUSIVE)

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

在这里插入图片描述

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);  //当前线程为B,节点为null
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail; //tail为null
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

将tail节点赋给pred节点,此时就为null,不会进入if,走入队方法enq(node)

private Node enq(final Node node) { //此时node为B顾客
    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;
            }
        }
    }
}

注意:这是一个自旋操作,

tail为空节点,会进入if判断,通过CAS操作设置head头结点的指向Node空节点(此时Node节点即图中的傀儡节点,不储存数据,仅用于占位)

private final boolean compareAndSetHead(Node update) { //此时传入的update为一个Node空节点
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

然后再将head头结点的执行赋给tail尾结点的指向

tail = head;

在这里插入图片描述

完成后,不会走下面的else 分支。由于是自旋,继续从头开始

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;//此时tail执行空节点,即不为null
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;//将B线程的前指针指向t节点(这里即tail节点)所执行的节点(这里即空节点)
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

tail不为null,走else分支,

首先:

node.prev = t;//将B线程的前指针指向空节点

在这里插入图片描述

然后:

compareAndSetTail(t, node) //设置尾结点:将tail尾结点所执向的节点改为执向顾客B

在这里插入图片描述

然后:

t.next = node; //将空节点的next指针指向顾客B

在这里插入图片描述

最后:return结束自旋!


此时,第三个顾客C,也来办理业务,同样也没有抢到锁,需要走到addWaiter(Node.EXCLUSIVE)方法

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail; //tail节点执行顾客B
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

此时,tail节点执行顾客B,赋给pred节点,所以pred节点也执行B,即pred不为null,需要进入if判断

首先:

node.prev = pred;//将顾客C的头指针指向顾客B

在这里插入图片描述

然后:

compareAndSetTail(pred, node)//设置尾结点指向顾客C

在这里插入图片描述

最后:

pred.next = node;//将顾客B的后指针指向顾客C

在这里插入图片描述

发现没有顾客C没有走enq(node)方法,也就是说此时已有哨兵节点,不需要再去创建哨兵节点进行占位。

若还有其他顾客D、E…走这条路依然是这样。


acquireQueued方法分析

虽然顾客B和顾客C依次都入了队,但是,没有真正的阻塞,下面开始执行acquireQueued()方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) { //此时传入进的node为顾客B
    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)
            cancelAcquire(node);
    }
}

这里又是一个自旋

首先:

final Node p = node.predecessor();//设置p为哨兵节点

//附上源码
final Node predecessor() throws NullPointerException {
    Node p = prev;//prev为头指针,将其指向的节点付给p
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}

然后:p=head相等,进入tryAcquire方法,再次尝试获取锁,假设现在依然抢不到锁,不能继续往下走,进入下一个if判断

if (shouldParkAfterFailedAcquire(p, node) &&
    parkAndCheckInterrupt())
    interrupted = true;
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//此时pred为哨兵节点
    int ws = pred.waitStatus;//此时为0
    if (ws == Node.SIGNAL)//-1
        /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
        return true;
    if (ws > 0) {
        /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else { //进入此语句
        /*
             * 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;
}

进入:

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//设置waitStatus为-1

由于是自旋,再次进入acquireQueued,尝试获取锁,获取失败,同理又进入shouldParkAfterFailedAcquire方法,但此时waitStatus值为1,所以进入下列if

if (ws == Node.SIGNAL)
    /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
    return true;

shouldParkAfterFailedAcquire返回为true,继续向下执行

if (shouldParkAfterFailedAcquire(p, node) &&
    parkAndCheckInterrupt())
    interrupted = true;


//此时:真正被阻塞
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

这个时候,才调用park()方法,将线程进行阻塞!!!

在这里插入图片描述

在这里插入图片描述

顾客C同理,都被阻塞在这里,直到拿到许可证,才可被依次放行


unlock()方法

此时顾客A办理完业务,准备释放锁,走到tryRelease方法

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

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

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

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//此时c就为0
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null); //将当前拥有锁的线程设置为null
    }
    setState(c); //同时设置state值为0
    return free;
}

返回true,进入release方法的if语句

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;//将头节点赋给h
        if (h != null && h.waitStatus != 0)//h的waitStarus状态值为-1
            unparkSuccessor(h);
        return true;
    }
    return false;
}

进入unparkSuccessor(h)方法:

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;  //此时为-1
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0); //进入,通过CAS操作将状态设置为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) //upark唤醒线程
        LockSupport.unpark(s.thread);
}

在这里插入图片描述


此时,顾客B和顾客C正挂起阻塞着,这里unpark后,相当于给了一张许可证

顾客B来个回马枪!!!

顾客B再次来到这个方法

final boolean acquireQueued(final Node node, int arg) { //顾客B
    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)
            cancelAcquire(node);
    }
}

尝试获取锁tryAcquire,来到nonfairTryAcquire方法

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            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;
}

此时state的状态值为0,顾客B进入if判断

if (c == 0) {
    if (compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
    }
}

将锁的线程设置当前线程B,返回true


即进入下列if判断

if (p == head && tryAcquire(arg)) {
    setHead(node);
    p.next = null; // help GC
    failed = false;
    return interrupted;
}

首先:

setHead(node);//设置头节点


//附上源码
private void setHead(Node node) {
    head = node; //将头节点指向顾客B
    node.thread = null; //将顾客B的线程设置为null
    node.prev = null;//前指针设置为null
}

在这里插入图片描述

然后:将哨兵节点的后指针设置为null,此时哨兵节点等待垃圾回收

p.next = null; // help GC

在这里插入图片描述

此时原顾客B节点就成为新的哨兵节点

同理,顾客C出队也是如此操作!!!


AQS考点

# 第一个考点我相信你应该看过源码了,那么AQS里面有个变量叫State,它的值有几种?

答 3个状态:没占用是0,占用了是1,大于1是可重入锁

# 第二个考点 如果AB两个线程进来了以后,请问这个总共有多少个Node节点?
答案是3个

更好的阅读体验,推荐语雀在线文档阅读AQS 源码解读


完整流程思维导图放在: https://www.processon.com/view/5fb6590f7d9c0857dda50442

说明:画这个图确实画了挺久,由于proccess发布克隆的最低为5RMB,并不能免费克隆,想要可以私聊我!!!

  • 20
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值