亮,有一计

文章 分类 评论
49 17 9

站点介绍

这是一个热爱和别人一起交流学习的程序员所建立的个人博客 欢迎大家交流讨论

AQS深度剖析

liang 2022-07-21 155 0条评论 java锁多线程知识juc多线程编码学习 AQS

首页 / 正文
本站的文章若无特殊标明转载的话均为原创,有些文章在我的csdn个人站也有,两者基本上是同步的 博客地址

发布于2022-07-08

是什么

AQS就是指的是JDk中的AbstractQueuedSynchronizer类,字面义是就是抽象队列同步器,是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石 那些锁的实现基本都用到了他。
他通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态

和他有关的锁

ReentrantLock,CountDownLatch,ReentrantReadWriteLock,Semaphore等等。基本都是这些锁类的内部有一个名称为Sync的内部类去继承这个AQS这个抽象类,并重写调用AQS类中的一些方法来实现自身的功能。

能做什么

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

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

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

初步解读

官网解释

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

AQS内部体系架构

AQS的int变量

AQS的同步状态State成员变量,0表示这个锁没有线程占用,1表示正在被占用 通过cas去修改该值

/**
     * The synchronization state.
     */
    private volatile int state;

AQS的CLH队列(fifo)

CLH队列(三个大牛的名字组成),为一个双向队列,注意他是先进先出的队列
fifo队列的描述

Node内部类

Node的int变量

Node的等待状态waitState成员变量,表示其他线程的等待状态 注意队列最后的node节点这个状态永远为0

volatile int waitStatus
属性说明

node内部类的属性说明

aqs图示

aqs图示

从ReentrantLock解读AQS

类图

aqs跟锁的类图关系

公平锁与非公平锁的区别

可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多走了一个方法hasQueuedPredecessors(),他是公平锁加锁时判断等待队列中是否存在有效节点的方法,如果已经存在节点了,公平锁会去直接排队,非公平锁会直接尝试去抢占锁,但是一旦线程进入了fifo队列,就会遵循先进先出的原则,要有先来后到。
比较代码

非公平锁源码解读

lock方法

    final void lock() {
        //直接上来就尝试抢占
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //抢占不到再去尝试或者进入fifo队列
            acquire(1);
    }

acquire方法

tryAcquire方法
这里注意一个重点 当current == getExclusiveOwnerThread()时表示是重入进来的锁,这时候会一直把state变量+1加上去,后面释放的时候也会一直减到0 这里就是为了支持可重入锁,否则你自己写个递归,然后自己把自己锁死了不就成了笑话了.
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;
    }
addWaiter(Node mode)

双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。

//新建立了一个用来占位的节点 此时头指针和尾指针都指向这个node节点
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;
                }
            }
        }
    }

后面再有线程进来
调用compareAndSetTail

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final AbstractQueuedSynchronizer.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);
        }
    }

shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法

private static boolean shouldParkAfterFailedAcquire(AbstractQueuedSynchronizer.Node pred, AbstractQueuedSynchronizer.Node node) {
        //获取前驱节点的状态
        int ws = pred.waitStatus;
        if (ws == AbstractQueuedSynchronizer.Node.SIGNAL)
            //如果是SIGNAL状态,等待被占用的资源释放,直接返回true
            //准备继续调用parkAndCheckInterrupt方法
            return true;
        //ws大于0说明是CANCELLED状态
        if (ws > 0) {
            //循环判断前驱节点是否也为CANCELLED状态忽略掉该状态的节点,重新连接队列
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //将当前节点设置为SIGNAL状态,用于后续唤醒操作
            //程序第一次执行到这返回为false,还会进行外层第二次循环,最终从第一个if返回
            compareAndSetWaitStatus(pred, ws, AbstractQueuedSynchronizer.Node.SIGNAL);
        }
        return false;
    }
    private final boolean parkAndCheckInterrupt() {
    //直接挂起线程 程序不会继续向下执行了
        LockSupport.park(this);
        //根据LockSupport的用法 程序在三种情况下会被唤醒继续执行
        //1.被unpark
        //2.被中断
        //3.其他不合逻辑的返回 异常等
        //如果被唤醒了 返回当前线程的中断状态,并清空中断状态
        // 如果是被中断的 该方法会返回true
        return Thread.interrupted();
    }

unLock方法

  • sync.release(1) 释放当前锁的占用线程,然后将锁占用标志state更新为0
  • unparkSuccessor(h) 判断有头结点并且头结点的waitStatus不为0 说明有后续节点,再判断后续节点不为null并且后续节点的等待状态正常,如果不正常就把下一个节点指向头结点直到找到能够被正常唤醒的节点,然后调用LockSupport.unpark(s.thread)唤醒

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

评论(0)

最新评论

  • Hello Word !

    我发现我的评论功能,加载不了头像!朋友圈和相册也加载不了,音乐播放器也不显示,求解!

  • il: Clark11370

    Margenemh 357 fotballdrakter til bedriftslag Coreyc – Coin Media – журнал о заработке в интернете Francesco CasieDalu maglia argentina AbbieLand JessRoman Website Darrellie PaulinaRu Profile of ChristieRy SidneyMgw ToneyKrog Liverpool Tøj - Blog Profile - iotaJots JaimieChe TriciaBri Wolearn: GregoryEmelia: Magliette Calcio Poco Prezzo SheltonTh WayneHone 438 Chile Tröja Barn Leopol – Profile – Primescool Forum CharlineC VitoHartf norge bortedrakt to receive adulation, always insists the result is most important and will therefore be thoroughly disheartened by this display. As is often the case in friendly matches, there was a lack FedericoW صفحه اصلی – Maglia Porto KurtBrise of urgency for large periods during this match, though Wales did link play promisingly in attack through Wilson and Brooks. Italy will be among the nations in pot one for the Euro 2020 qualifying draw in Dublin on 2 December having drawn 0-0 against Portugal in their previous match to ensure a second-place finish in Nations League Group 3A. TanjaFitz Elizabeth manchester city tröjor KenPoulin CarenWhar Maglia Juventus Bambino EmeliaBow Stephaine liverpool drakt DarlaFulm FerneSuvg barcelona FC drakt Meaganold EmilyEise arsenal drakt KourtneyS Kellyejoh inter trøje JamaalWor Loisniyet psg drakt MaricelaS CecileSwa lavora come produttore esecutivo della serie CharissaC ChastityF Home - Liverpool Drakt EmilGmvut CasieDalu maglia argentina AbbieLand UrsulaLof rashford numero maglia KareemCap SSNGilber arsenal drakt 2022 MelodeePe

  • arsenal trøjer

    BrittnyFo atletico madrid tröja ClaritaBu ZJRBridge magliette psg JosephDur Valentina real madrid drakt MarionTol NevaLangt manchester united drakt BraydenBo RodolfoCa Real Madrid Tröja KelliefjL BettinaMi Notenbrood op top niveau MillardMh RichardMo arsenal drakt Bradleyjo TanyaNuge Spanien landsholdstrøje AprilBuck JoannaShe Billige Polen Fotballdrakter NiklasWes Maryellen terza maglia milan PrincessN AnhGiffen real madrid tröja FredricAu JoannaShe Billige Polen Fotballdrakter NiklasWes EssieOate juventus matchtröja PreciousF EdnaTippe sabinahogan447 EffieOcon MichaelaH liverpool tröja barn KayleighM JonnieHoc nuova maglia roma 2021/22 CortezSha YDZClaire ac milan drakt KathleneH CedricBac manchester united drakt Peterysjl HaicoQcmw Maglia Brasile Femmina ThereseJu DoreenMes napoli trøje JadaLanni

  • maglie hellas verona 2022

    Your style is very unique in comparison to other folks I have read stuff from. Many thanks for posting when you have the opportunity, Guess I'll just bookmark this blog. maglie hellas verona 2022 IlanaGxrn frankrike tröja JaneenZtt

  • inter milan tröja

    I truly love your site.. Pleasant colors & theme. Did you make this website yourself? Please reply back as I'm hoping to create my own website and would like to learn where you got this from or just what the theme is named. Cheers! inter milan tröja LilianaCz Frankrike Tröja Barn NganRjpfi

日历

2022年12月

    123
45678910
11121314151617
18192021222324
25262728293031

友情链接

文章目录

推荐关键字:

站点公告
本站的文章若无特殊标明转载的话均为原创,有些文章在我的csdn个人站也有,两者基本上是同步的 博客地址
点击小铃铛关闭