AQS的源码解析(自己的理解)

AQS的源码解析(自己的理解)

一:AQS的前置知识

AQS涉及到以下知识点:如不清楚可以去B站上看尚硅谷的大厂面试第二季juc

1.juc
2.LockSupper
3.模板模式
4.CAS

二:AQS是什么

字面意思:

  • Abstract Queued Synchronizer(抽象的队列同步器)

实现原理:

  • status成员变量+CLH队列变种 FIFO双向虚拟队列

简单流程理解:

通过控制stauts变量来表示锁的状态,0代表资源开发 1代表目前有人占用资源 默认初始化0,抢不到线程的资源就在队列中等待释放资源进行抢占

三:AQS有什么用?用来做什么

那些协作类,它们有很多工作是类似的,所以如果能把实现类似工作的代码给提取出来,变成一个新的底层工具类(或称为框架)的话,就可以直接使用这个工具类来构建上层代码了,而这个工具类其实就是 AQS

有了 AQS 之后,对于 ReentrantLock 和 Semaphore 等线程协作工具类而言,它们就不需要关心这么多的线程调度细节,只需要实现它们各自的设计逻辑即可

简单来说 AQS是用来构建锁或者其他同步组件的基础框架

四:AQS的源码解析

1、 AQS的内部体系关系

下面是精炼出来的内部体系关系内容:省略了很多

//AQS
public abstract class AbstractQueuedSynchronizer{
    private transient volatile Node head;//头节点
    private transient volatile Node tail;//尾节点
    private volatile int state;//资源State变量,默认值为0
    private static final Unsafe unsafe = Unsafe.getUnsafe();//直接操作内存的unsafe工具包
    static final class Node {
        volatile int waitStatus;//线程Node节点状态
        volatile Node prev;//前节点
        volatile Node next;//后节点
        volatile Thread thread;//Node节点的线程
    }
}

上面会看到,AQS中会将线程进行包装成一个Node节点,它有前/后节点。这里就会发现他没有一个真正的双向队列,而是通过包装线程为Node类,并指定里面前后的指向,用前/后节点指针指向来实现一个虚拟的双向队列。

  • node节点中存放的都是一个个Thread

小知识 map<k,v>的put方法存的是node节点 而node节点中存放k,v

2、 ReentrantLock和AQS的关系

首先我们来看下ReentrantLock的源码

由此看出他内部创建了一个内部类sync sync继承了AQS

3、AQS队列的基本结构

在这里插入图片描述

4、通过两个线程抢资源锁来演示

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

        new Thread(()->{
            try {
                lock.lock();
                System.out.println("模拟复杂的业务逻辑,20分钟后完成");
                try { Thread.sleep(60*1000*20); }catch (InterruptedException e){ e.printStackTrace();}
                System.out.println("已执行完毕,释放资源");
            }  finally {
                lock.unlock();
            }
        },"A");
        
        new Thread(()->{
            try {
                System.out.println("线程B进行抢占");
                lock.lock();
            }finally {
                lock.unlock();
            }
        },"B");
    }

按照代码的执行顺序,先进入A线程里执行lock.lock()方法,我们看看lock()方法的底层到底做了什么?

① 线程A进入lock方法

在这里插入图片描述
进来就会执行内部类sync的lock方法,点进sync的lock中去
在这里插入图片描述

映入眼帘的很明显的就是一个抽象方法 它有两个实现类 公平锁FairSync和非公平锁NonfairSync 默认的是非公平锁NonfairSync
在这里插入图片描述
因为ReentantLock的无参构造就是NonfairSync 我们继续往下走
在这里插入图片描述
线程进来后先进行判断 执行compareAndSetState(0, 1) 比较并交换 第一次status的值是0 那么将会将status的值设置成1并返回true
返回true之后 执行setExclusiveOwnerThread(Thread.currentThread()); 设置占有资源的线程为当前线程
在这里插入图片描述
此刻代码已经执行完A线程的lock方法 模拟执行复杂的业务逻辑20分钟,那么该线程将一直持有锁
目前的流程图应该是这样的
在这里插入图片描述

②线程B执行lock方法

线程B执行到了这里
在这里插入图片描述
判断目前status=0吗 肯定不等于,因为线程A已经将status修改成1 所以走else的 acquire() 方法

㈠acquire方法

在这里插入图片描述
这里有三个方法

  • tryAcquire()
  • addWaiter()
  • acquireQueued()

我们先进入tryAcquire()方法中
在这里插入图片描述

tryAcquire方法是一个模板设置模式方法 他强制让子类实现该方法 否则就会抛出 UnsupportedOperationException() 异常 由于无参构造是非公平锁NonfairSync ,进入之后就会走nonfairTryAcquire() 方法
在这里插入图片描述

final boolean nonfairTryAcquire(int acquires) {//acquires是1
			//获取当前线程(当前线程是线程B)
            final Thread current = Thread.currentThread();
            //获取当前stauts的值(现在status的值是1)
            int c = getState();
            //status等于0后进入
            if (c == 0) {
            	//从名字就可以看出 又是一个cas方法 将传进来的1和0进行比较并且交换
                if (compareAndSetState(0, acquires)) {
                	//设置占有资源的线程为当前线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //判断是否是可重入锁
            //当前线程是否与占有资源的线程是同一个线程
            //当前线程是线程B,占有线程是线程A
            //如果线程A由于一些原因再取获取同一把锁将会进入下面逻辑
            else if (current == getExclusiveOwnerThread()) {
            	//如果是可重入锁,那么status的值则累加
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
           //当前变量是1 所以返回false
            return false;
        }

我们回到acquire() 方法中
在这里插入图片描述
tryAcquire() 方法返回的是false 但是取反后为true,接着进入addWaiter() 方法中
Node.EXCLUSIVE 的值为null 排他类型

 private Node addWaiter(Node mode) {
 		//将线程B封装成一个node节点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
     //获取队列的尾节点 当前队列中尾节点没有指向任何一个node 那么这个pred肯定是null
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //尾节点为null 进入enq方法中 将node<线程B>节点加入队列
        enq(node);
        return node;
    }
㈡enq()方法
private Node enq(final Node node) {//node是node<线程B>
        for (;;) {//自旋
        	//获取当前队列中的尾节点 t=null
            Node t = tail;
            //判断成立 执行下方逻辑
            if (t == null) { // Must initialize 
            //初始化一个空节点 new Node(),作为队列中的哨兵节点
                if (compareAndSetHead(new Node())) //cas操作,设置头节点为空节点
                    tail = head;//尾节点设置为头节点指向,也就是哨兵节点
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

目前队列中将会多出一个哨兵节点,整体状态图为:
在这里插入图片描述
由于是 自旋 那么node<线程B>第二次运行的时候

  private Node enq(final Node node) {
  		//第二次进来
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {//就会执行到这里
            	//将node<线程B>的prev前节点指向t也就是哨兵节点
                node.prev = t;
                //cas方法 把tail尾节点设置
                if (compareAndSetTail(t, node)) {
                	//将哨兵节点的next后节点指向给node<线程B>
                    t.next = node;
                    return t;
                }
            }
        }
    }

目前整体状态图为
在这里插入图片描述
此时addWaiter() 方法已执行完毕 接下来执行acquireQueued()

㈢acquireQueued() 方法

这个方法最为重要,他的主要作用是将线程抢占不到资源后挂起

 final boolean acquireQueued(final Node node, int arg) {//node<线程b> arg=1
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            for (;;) {
            	//获取当前node的前节点 当前线程B的前节点是哨兵节点
                final Node p = node.predecessor();
                //判断前节点是不是头节点 又执行了一次tryAcquire()方法
                //因为tryAcquire会再次抢锁,内容跟上面的一样
                //因为线程A一直占用着资源,所以不进入逻辑
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //p是哨兵节点 node是node<线程B>
                //判断此时哨兵节点的waitStatus的值是不是不等于0
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

根据上面代码会进入shouldParkAfterFailedAcquire()方法

㈣shouldParkAfterFailedAcquire()方法
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 		//pred是哨兵节点 node是线程B
 		//获取哨兵节点的ws 默认是0
        int ws = pred.waitStatus;
        //SIGNAL = -1 条件不成立
        if (ws == Node.SIGNAL)
         
            return true;
         //ws =0 条件不成立
        if (ws > 0) {
            
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //cas方法 将哨兵节点的ws设置成-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

此时队列的状态图为:

在这里插入图片描述
shouldParkAfterFailedAcquire()返回false后 回到了acquireQueued()方法

 final boolean acquireQueued(final Node node, int arg) {//node<线程b> arg=1
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋 第二次进来
            for (;;) {
            	//获取当前node的前节点 当前线程B的前节点是哨兵节点
                final Node p = node.predecessor();
                //判断前节点是不是头节点 又执行了一次tryAcquire()方法
                //因为tryAcquire会再次抢锁,内容跟上面的一样
                //因为线程A一直占用着资源,所以不进入逻辑
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //p是哨兵节点 node是node<线程B>
                //判断此时哨兵节点的waitStatus的值是不是不等于0
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

再一次进入shouldParkAfterFailedAcquire()

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 		//pred是哨兵节点 node是线程B
 		//获取哨兵节点的ws 此时的ws值为-1
        int ws = pred.waitStatus;
        //SIGNAL = -1 条件成立
        if (ws == Node.SIGNAL)
         	//返回true
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
           
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

又回到acquireQueued方法这里 由于shouldParkAfterFailedAcquire返回是true 条件成立执行parkAndCheckInterrupt方法
在这里插入图片描述

㈤parkAndCheckInterrupt() 方法

在这里插入图片描述
这个this就是线程B
LockSuppot.park将线程B阻塞到这里 除非触发LockSuppot.unpark方法将线程B唤醒
AQS此时状态图是:
在这里插入图片描述

③线程A释放资源

线程A执行完业务逻辑后就会执行 unlock方法
在这里插入图片描述

㈠ unlock方法

在这里插入图片描述
看源码unlock方法依然是调用sync的release方法

㈡release方法

在这里插入图片描述
进来release方法中,先执行if的tryRelease方法
在这里插入图片描述
这个方法又是一个标准的模板设计模式跟之前的tryAcquire大同小异

㈢ tryRelease方法

因为我们是通过ReentrantLock进行演示,所以进入下面这个方法中

protected final boolean tryRelease(int releases) { //releases = 1
			//getState获取当前的stauts 值 =1 
            int c = getState() - releases;
            //判断当前线程是否是资源占用线程
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //根据上面的减法我们知道 c =0 进入以下逻辑
            if (c == 0) {
                free = true;
               //将当前占用资源的线程改为null
                setExclusiveOwnerThread(null);
            }
            //设置status的值为0
            setState(c);
            return free;
        }

当前全局状态图:
在这里插入图片描述
我们再回到release方法中

public final boolean release(int arg) {
		//返回为true
        if (tryRelease(arg)) {
        	//取出头节点 也就是哨兵节点 
            Node h = head;
            //哨兵节点不等于null,并且他的ws是-1 条件不成立
            if (h != null && h.waitStatus != 0)
            	//唤醒阻塞的线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
㈣unparkSuccessor方法
private void unparkSuccessor(Node node) {//哨兵节点
		//获取哨兵节点的ws ws = -1
        int ws = node.waitStatus;
        //ws = -1 条件成立
        if (ws < 0)
        	//cas方法 将哨兵节点ws的值设置成0
            compareAndSetWaitStatus(node, ws, 0);
        //获取哨兵节点的后一个节点 也就是线程B
        Node s = node.next;
        //线程B不为空 ws值是0 条件不成立
        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)
        	//将线程B唤醒
            LockSupport.unpark(s.thread);
    }

LockSupport.unpark(s.thread)把线程B给唤醒 来进行抢占资源
此时状态图为
在这里插入图片描述

④线程B被唤醒,抢占资源

由于上面线程b被park阻塞在acquireQueued这个方法中
我们再来看看acquireQueued方法

㈠acquireQueued方法

在这里插入图片描述
线程b在自旋死循环,现在被线程A唤醒,进行抢占资源

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //线程B被唤醒 又进入循环
            for (;;) {
            	//获取线程B的前节点,也就是哨兵节点
                final Node p = node.predecessor();
                //哨兵节点是头节点,
                //再次执行tryAcquire抢占资源,tryAcquire返回true
                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方法

上面讲过tryAcquire是一个模板模式方法 所以我们进入他的实现方法nonfairTryAcquire方法中

final boolean nonfairTryAcquire(int acquires) {
			//拿到线程B
            final Thread current = Thread.currentThread();
            //获取成员变量status的值 stauts = 0
            int c = getState();
            //条件成立
            if (c == 0) {
            	//cas 比较并交换 将stauts设置成1
                if (compareAndSetState(0, acquires)) {
                	//占有资源的线程设置成线程B
                    setExclusiveOwnerThread(current);
                    //返回true
                    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之后我们再次进入acquireQueued方法中

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //执行tryAcquire方法 返回了true
                if (p == head && tryAcquire(arg)) {
                	//线程B设置成头节点
                    setHead(node);
                    //将哨兵节点的后节点的指向设置成NUll 
                    //这样的话,哨兵节点就没有任何指向了 等待GC回收
                    p.next = null; // help GC
                    failed = false;
                    返回是否被中断
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
㈢setHead方法
 private void setHead(Node node) {
 		//线程B设置成头节点
        head = node;
        //node<线程B> = node<null>
        node.thread = null;
        //线程B的前节点设置成空 
        node.prev = null;
    }

目前全局状态图为
在这里插入图片描述
以此循环,如果有别的线程加入队列也是维持以上的流转操作。

五:AQS总结

上面的流程就是通过ReentrantLock来演示AQS抢占资源和释放资源的过程。
AQS的流程用一句话来解释就是:通过资源变量State+AQS的同步管理队列,实现了线程抢占资源的管理
用成员变量 status 来控制 此时资源是否有线程抢占,如果是0就是没有线程占有资源
如果大于等于1 就是资源已经被占用
没有占用资源的线程就放入队列中进入等候,通过阻塞线程和释放线程来控制队列中的线程。

  • 22
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值