AQS(AbstractQueuedSynchronizer)抽象队列同步器

前言

AQS 绝对是JUC的重要基石,也是面试中经常被问到的,所以我们要搞清楚这个AQS到底是什么?骑工作原理是什么?

AQS是什么?

是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO对列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态。
在这里插入图片描述
CLH队列:CLH(Craig, Landin, and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。通过CAS完成对State值的修改。
同步对列的内部结构及继承关系
在这里插入图片描述
在这里插入图片描述

用银行办理业务的案例模拟AQS如何进行线程管理和通知机制

1、初始化的时候,state = 0 (0 表示没有人,1表示有人),线程池也有没有人在执行,现在就是没有顾客的时候,因此第一个线程去的时候都是公平锁状态直接到窗口办理业务。
在这里插入图片描述
2、现在有一个线程Thread A进来那么就直接到了业务窗口去办理,并且通过CAS将state的值变成1
在这里插入图片描述
3、此时有一个线程Thread B 进来,通过getStatue() 方法查看到state = 1,此时ThreadA有在占用着,所以现在ThreadB线程就必须先入队等待ThreadA结束后再调用,但是由于现在队列是空的,所以ThreadB线程并不会马上进入到队列,他会先进入addWaiter() 方法到enq()这个方法中去,这个方法实质上就是一个自旋锁,在这个方法中主要的时候先要实现一个队列的初始化工作,先形成一个傀儡结点(哨兵结点)起到一个占位的作用,然后才能将ThreadB线程挂在后面。
在这里插入图片描述
部分源码附上:

	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的前指针赋值过去
     if (pred != null) { //一开始的时候并没有指向所以位null,因此条件不成立不走里面的语句
         node.prev = pred;
         if (compareAndSetTail(pred, node)) {//CAS
             pred.next = node;
             return node;
         }
     }
     enq(node);//一开始会来走这个enq()这个方法。
     return node;
 }

因为当前的ThreadB线程进入时,tail的prev指针为null所以就会进enq()方法。如下:

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

4、ThreadC线程开始入队,那么这个就是跟ThreadB是一样的,只不过是说没有了初始化那步了,直接挂在线程ThreadB上即可。
在这里插入图片描述
5、ThreadB 会去尝试acquireQueued()这个方法,那么第一次的时候会将哨兵结点的waitStatus = -1; 然后继续自旋,进入到if条件的下一个条件中被调用park() 阻塞掉,等待着ThreadA 线程的unpark()。这样才算真正的入列等待了。
在这里插入图片描述

	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()) //第一次的时候会阻塞在前面的条件并将哨兵的waitState置为-1,第二次的话第一个条件为真,所以会走第二个判断条件,会将访问的线程给阻塞掉等待unpark();
                 interrupted = true;
         }
     } finally {
         if (failed)
             cancelAcquire(node);
     }
 }
	private final boolean parkAndCheckInterrupt() {
     LockSupport.park(this); //将访问次方法的线程给阻塞掉,等待unpark()方法唤醒
     return Thread.interrupted();
 }

6、当ThreadA线程办好业务的时候那么就会调用unlock()方法释放锁,unlock() 方法中又会调用sync.release()方法,并将当前窗口的线程变成null,state 置成0,通过调用 unparkSuccessor()方法将 傀儡结点的 waitState = 0
在这里插入图片描述

	private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        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); //释放锁唤醒等待队列中的等待线程
    }

7、接着会调用unpark()方法将在前面等待的ThreadB线程给唤醒去窗口办业务。将head结点指向ThreadB结点,ThreadB的prev结点为null,next结点也为null,ThreadB变成空结点,此节点就成了新的哨兵结点,原来的哨兵结点被GC回收。
在这里插入图片描述
8、ThreadC线程出列和上面的过程是一样的。

结语

AQS的核心大致是这样,如果说我讲的你还是没有听懂的话,可以去B站看善硅谷阳哥的高频面试题那集去看看。我只是将其做了一个简化,用自己的话把这个过程复述出来,希望能对你有所帮助。
收工~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

聪明不喝牛奶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值