Java并发之AQS


文章目录

  • 一:AQS简介
  • 二:了解AQS 上锁和释放锁的原理
    • 1:前言
    • 2:上锁(非公平锁)
      • (1):我们从main主函数中点进去
      • (2):从lock进入
      • (3):找到非公平锁中的lock
      • (4):查看acquire()方法
      • (5):查看tryAcquire(arg)方法
        • a:前言介绍
        • b:进入ReentrantLock类中的nonfairTryAcquire方法
      • (6):addWaiter(Node.EXCLUSIVE)
        • a:前言
        • b:源码走一波
      • (7): acquireQueued(Node, int)
        • a:acquireQueued()
        • b: shouldParkAfterFailedAcquire(pred, Node)
        • c:parkAndCheckInterrupt()
        • d:小结 acquireQueued(),总结下该函数的具体流程:
      • (8):总结
    • 3:公平锁
    • 4:释放锁
      • (1):从main函数进入
      • (2):在进入Release(ReentrantLock的类中)
      • (3):进入AQS源码中
      • (4): unparkSuccessor(h)
        • a:源码解析:
        • b:为啥处于等待结点时候是逆序
      • (5):小结


一:AQS简介

  • AQS全称为AbstractQueuedSynchronizer,它是一个一个抽象类
  • 在AQS中有几个属性和一个双向队列(CLH队列)
//头节点
private transient volatile Node head;
//尾节点
private transient volatile Node tail;
//状态值
private volatile int state;
  • AQS是一个基类,在JUC并发包下,其实现类有ReentrantLock,CountDownLatch,…
  • 图示例
    在这里插入图片描述

二:了解AQS 上锁和释放锁的原理

1:前言

我们是通过AQS的实现类ReentrantLock来进行解析其上锁 和 释放锁的原理,但是ReetrantLock锁是有公平锁和非公平锁,以及可重入锁的分类的 我们先看非公平锁

2:上锁(非公平锁)

(1):我们从main主函数中点进去

 public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();lock.lock();//点进去lock看源码}

(2):从lock进入

public void lock() {sync.lock();}
  • 解释:我们这里从lock直接点进去看源码的话,其实是进了 sync的,该sync类中是定义了lock这个方法的,并且syn 内部类是继承自AbstractQueuedSynchronizer类,那我们的非公平锁类和公平锁类是继承自 sync 这个类的,并且实现其lock方法 。

(3):找到非公平锁中的lock

 final void lock() {//以CAS的方式尝试将 state 状态从 0改为1,如果返回 true 则说明上锁成功,否则该资源有线程占用修改失败if (compareAndSetState(0, 1))//证明当前线程拿到了锁资源setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}

(4):查看acquire()方法

 public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
  • tryAcquire()尝试直接去获取资源,如果成功则直接返回(这里体现了非公平锁,每个线程获取锁时会尝试直接抢占加塞一次,而CLH队列中可能还有别的线程在等待);
  • 如果尝试获取资源失败的话 也就是 tryAcquire(arg) = false 而 !tryAcquire(arg) =true ,那么接下来就将该线程封装成一个结点Node将其追加到AQS的队列后面中。

(5):查看tryAcquire(arg)方法

a:前言介绍

  • 我们点击tryAcquire()的话,我们是直接进入到的是 AQS 基类的 tryAcquire(),这里并没有给出什么具体的实现;但是其实现类
    ReentrantLock中 是有其实现的,所以我们需要在ReentrantLock中寻找tryAcquire()的方法
    在这里插入图片描述

b:进入ReentrantLock类中的nonfairTryAcquire方法

final boolean nonfairTryAcquire(int acquires) {//获取到当前的线程final Thread current = Thread.currentThread();//获取到AQS的state值int c = getState();//c == 0 证明此时无锁,我们可以通过CAS对某个资源进行上锁if (c == 0) {if (compareAndSetState(0, acquires)) {//证明当前线程拿到了资源setExclusiveOwnerThread(current);return true;}}//锁重入了,  就是我们同一个线程对一个资源上了多次锁else if (current == getExclusiveOwnerThread()) {//将State + 1int nextc = c + acquires;//当c是最大值的时候 再+1的话,那么在二进制中 符号位就会从1到0,那么的话整个值就为负数if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");//重新对AQS中的state值进行赋值setState(nextc);return true;}return false;}

(6):addWaiter(Node.EXCLUSIVE)

a:前言

如果尝试获取资源失败的话,那么我们将该线程封装成Node结点,并将其放到AQS队列尾部

b:源码走一波

private Node addWaiter(Node mode) {//这里就是将当前线程封装成一个结点,mode有两种:EXCLUSIVE(独占)和SHARED(共享),在reentrantLock中,mode 是独占锁Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failure//获取到AQS队列的尾部结点Node pred = tail;//如果队列不空的话if (pred != null) {//让新的结点指向AQS尾部结点(双向队列)node.prev = pred;//因为AQS队列中tail一直指向的是最后一个结点,所以我们通过CAS操作将 tail指向最后一个结点if (compareAndSetTail(pred, node)) {//让倒数第二个结点指向倒数第一个结点pred.next = node;return node;}}//如果队列为空的话 或者是CAS操作失败的话,enq(node);return node;}

如果队列为空的话 或者是CAS操作失败的话,则进入enq()函数;

private Node enq(final Node node) {//死循环,CAS通过自旋的方式一定将结点成功的插入到队列尾部,for (;;) {//获取队列的尾部结点 Node t = tail;if (t == null) { // Must initialize 初始化完成后 tail和head都指向该node结点if (compareAndSetHead(new Node()))tail = head;} else { //else 就是上个函数CAS操作失败了,或者是初始化的时候,发生线程上下文切换,那么的话我们执行下列代码node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}

(7): acquireQueued(Node, int)

a:acquireQueued()

通过tryAcquire()和addWaiter(),该线程获取资源失败,已经被放入等待队列尾部了,进入等待状态休息,直到其他线程彻底释放资源后唤醒自己,自己再拿到资源,然后就可以去干自己想干的事了(跟医院排队拿号有点相似~~acquireQueued()就是干这件事:在等待队列中排队拿号(中间没其它事干可以休息),直到拿到号后再返回)

 1 final boolean acquireQueued(final Node node, int arg) {2     boolean failed = true;//标记是否成功拿到资源3     try {4         boolean interrupted = false;//标记等待过程中是否被中断过5         6         //又是一个“自旋”!7         for (;;) {8             final Node p = node.predecessor();//拿到前驱9             //如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源(可能是老大释放完资源唤醒自己的,当然也可能被interrupt了)。
10             if (p == head && tryAcquire(arg)) {
11                 setHead(node);//拿到资源后,将head指向该结点。所以head所指的标杆结点,就是当前获取到资源的那个结点或null。
12                 p.next = null; // setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了!
13                 failed = false; // 成功获取资源
14                 return interrupted;//返回等待过程中是否被中断过
15             }
16             
17             //这里需要保证上一个结点是的-1 (SIGNAL    = -1 这个-1代表上一个锁是被唤醒的状态), 当是-1的时候返回true
18             if (shouldParkAfterFailedAcquire(p, node) &&
19                 parkAndCheckInterrupt())//基于unsafe类的park()方法,阻塞线程,直到被unpark()唤醒。
20                 interrupted = true;//如果等待过程中被中断过,哪怕只有那么一次,就将interrupted标记为true
21         }
22     } finally {
23         if (failed) // 如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。
24             cancelAcquire(node);
25     }
26 }

b: shouldParkAfterFailedAcquire(pred, Node)

此方法主要用于检查状态,看看自己是否真的可以去休息了(进入waiting状态),万一队列前边的线程都放弃了只是瞎站着,那也说不定,对吧。(如果前驱节点pred的状态不是-1的话,那么我们的Node就往前进寻找,直到 找到一个前驱结点是-1的,然后将Node放到其后面)

1 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {2     int ws = pred.waitStatus;//拿到前驱的状态3     if (ws == Node.SIGNAL)//Node.SIGNAL == -14         //如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了5         return true;6     if (ws > 0) {7         /*8          * 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。9          * 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)!
10          */
11         do {
12             node.prev = pred = pred.prev;
13         } while (pred.waitStatus > 0);
14         pred.next = node;
15     } else {
16          //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
17         compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
18     }
19     return false;
20 }

整个流程中,如果前驱结点的状态不是SIGNAL,那么自己就不能安心去休息,需要去找个安心的休息点,同时可以再尝试下看有没有机会轮到自己拿号。

c:parkAndCheckInterrupt()

如果线程找好安全休息点后,那就可以安心去休息了。此方法就是让线程去休息,真正进入等待状态

private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}

park()会让当前线程进入waiting状态。在此状态下,有两种途径可以唤醒该线程:1)被unpark();2)被interrupt()。

d:小结 acquireQueued(),总结下该函数的具体流程:

  • 结点进入队尾后,检查状态,找到安全休息点;
  • 调用park()进入waiting状态,等待unpark()或interrupt()唤醒自己;
  • 被唤醒后,看自己是不是有资格能拿到号。如果拿到,head指向当前结点,然后获取该锁资源;如果没拿到,继续流程1,继续等待。

(8):总结

1 public final void acquire(int arg) {
2     if (!tryAcquire(arg) &&
3         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
4         selfInterrupt();
5 }
  • 调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
  • 没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
  • acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
  • 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
    在这里插入图片描述

3:公平锁

从源码中我们可以观察出,公平锁是在上锁的时候,并没有一上来就先去尝试获取资源,而是直接进入 acquire(1)

 static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}}

4:释放锁

(1):从main函数进入

 ReentrantLock lock = new ReentrantLock();lock.lock();lock.unlock();//从这里进入

(2):在进入Release(ReentrantLock的类中)

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

(3):进入AQS源码中

  public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;//找到头节点if (h != null && h.waitStatus != 0)unparkSuccessor(h);//唤醒下一个线程	return true;}return false;}

(4): unparkSuccessor(h)

a:源码解析:

private void unparkSuccessor(Node node) {int ws = node.waitStatus;//获取到线程的状态, 小于0 说明数处于等待状态,大于0说明说明该结点已经放弃等待if (ws < 0)compareAndSetWaitStatus(node, ws, 0);//0状态就是已经完成的状态//得到头结点的后继结点head.nextNode s = node.next;//如果这个后继结点为空或者状态⼤于0//通过前⾯的定义我们知道 ⼤于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)LockSupport.unpark(s.thread);}

b:为啥处于等待结点时候是逆序

  • 由于并发问题,addWaiter()入队操作和cancelAcquire()取消排队操作都会造成next链的不一致,而prev链是强一致的,所以这时从后往前找是最安全的。
    在这里插入图片描述

(5):小结

release()是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了==(即state=0)==,它会唤醒等待队列里的其他线程来获取资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307458.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络知识 | 《图解TCP/IP》读书笔记(上)

【网络知识】| 作者 / Edison Zhou这是EdisonTalk的第290篇原创内容作为一个专业的IT技术人&#xff0c;一个Web应用开发者&#xff0c;不了解网络基础和协议&#xff0c;怎么能行&#xff1f;本文是我2016年阅读《图解TCP/IP》一书的读书笔记&#xff0c;希望对你有所帮助&…

Java并发之semaphore(信号量)

文章目录1:官方解读2:通俗易懂的例子解析3:代码解析4:Semaphore的应用5:类结构和相关方法(1):类结构(2):acquire()方法(3):release()方法6:总结1:官方解读 semaphore信号量就是并发工具类,Semaphore管理着一组许可permit&#xff0c;许可的初始数量通过构造函数设定。 当线程要…

IdentityServer4系列 | 初识基础知识点

前言我们现在日常生活中&#xff0c;会使用各式各样的应用程序&#xff0c;层出不穷&#xff0c;其中有基于网页浏览方式的应用&#xff0c;有基于手机端的App&#xff0c;甚至有基于流行的公众号和小程序等等&#xff0c;这些应用&#xff0c;我们不仅要实现各个应用的功能之外…

八大排序算法之终章---归并排序

一:简单介绍 归并排序排序就是利用归并的思想实现的排序方法 它的原理是将初始化序列划分成n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并&#xff0c;得到(n/2)个长度为1或者2的有序子序列;然后再两两合并…不断重复 直到最后 得到一个长度为n的有序…

Firefox 18周岁

Mozilla Firefox 起源于开源运动兴起之初建立的一个项目组织——Mozilla 社区&#xff0c;可以说是最早以“开源”名义出现&#xff0c;并取得成功的项目之一。Firefox 首次发行是在2002年的9月23日&#xff0c;当时的代号为“Phoenix”&#xff08;凤凰&#xff09;。18年过去…

三种方式让你轻松监控 EntityFramework 中的 sql 流转

大家在学习entityframework的时候&#xff0c;都知道那linq写的叫一个爽&#xff0c;再也不用区分不同RDMS的sql版本差异了&#xff0c;但是呢&#xff0c;高效率带来了差灵活性&#xff0c;我们无法控制sql的生成策略&#xff0c;所以必须不要让自己缺乏好的工具去监控sql&…

java并发之CountdownLatch

一:CountdownLatch 1:基本含义 CountDownLatch中count down是倒数的意思&#xff0c;latch则是门闩的含义。整体含义可以理解为倒数的门栓&#xff0c;似乎有一点“三二一&#xff0c;芝麻开门”的感觉。CountDownLatch的作用也是如此&#xff0c;在构造CountDownLatch的时候…

leetcode142. 环形链表 II(详解)

一:题目 二:思路分析 三:上码 class Solution { public:ListNode *detectCycle(ListNode *head) {ListNode* slow head;ListNode* fast head;while (fast ! NULL && fast->next ! NULL && fast->next->next ! NULL) {//这里选用快指针fast fast-&g…

国产化之路-统信UOS /Nginx /Asp.Net Core+ EF Core 3.1/达梦DM8实现简单增删改查操作

引言经过前期的准备工作&#xff0c;.net core 3.1的运行环境和WEB服务器已经搭建完毕&#xff0c;这里需要注意一下&#xff0c;达梦DM8数据库对于Entity Framework Core 3.1 的驱动在NuGet官方源上并没有正式发布&#xff0c;需要从Win64安装版本中的drivers/dotNet提取&…

网络知识 | 《图解TCP/IP》读书笔记(下)

【网络知识】| 作者 / Edison Zhou这是EdisonTalk的第291篇原创内容作为一个专业的IT技术人&#xff0c;一个Web应用开发者&#xff0c;不了解网络基础和协议&#xff0c;怎么能行&#xff1f;本文是我2016年阅读《图解TCP/IP》一书的读书笔记下半部分。上半部分&#xff1a;点…

leetcode202. 快乐数(详解)

一:题目 二:上码 class Solution { public:/**思路&#xff1a;1.破解这道题的关键是 我们得破解这个无限循环2.根据这个题目给出的定义2 我们可以知道 无限循环的条件是我们 在计算的过程中出现了自己的数*/bool isHappy(int n) {unordered_set<int>s;s.insert(n);…

自由软件基金会庆祝成立35周年

美国东部时间 10 月 4 日&#xff0c;自由软件基金会&#xff08;Free Software Foundation&#xff0c;FSF&#xff09;在官网发文庆祝”其为争取软件自由而奋斗的第 35 年“。1985 年 10 月&#xff0c;自由软件运动的主要发起人 RMS&#xff08;Richard Matthew Stallman&am…

使用 C# sdk 连接 高可用的 rabbitmq 镜像集群

我们知道rabbitmq是一个专业的MQ产品&#xff0c;而且它也是一个严格遵守AMQP协议的玩意&#xff0c;但是要想高端大气上档次&#xff0c;一定需要拿出高可用的东西出来&#xff0c;这不本篇就跟大家说一下cluster的概念&#xff0c;rabbitmq是erlang写的一个成品&#xff0c;所…

java并发之CyclicBarrier(通俗易懂)

文章目录一:简介(1):官方解释(2):通俗解释a:CountDownLatch()b:CyclicBarrier()二:小demo一:简介 (1):官方解释 说到简介那就非得拿他和countdownlatch()进行比较讨论了CountDownLatch是一个同步的辅助类&#xff0c;允许一个或多个线程&#xff0c;等待其他一组线程完成操作…

BeetleX之简单HTTP/HTTPS实现

在通讯应用中很多时候需要和已有标准的应用协议进行通讯&#xff0c;针对这情况就要针对相应协议的实现&#xff1b;标准协议上考虑的情况比较多&#xff0c;所以协议的复杂度也相对高些&#xff0c;对比之前的Protobuf通讯的简单协议来说则会复杂。接下来用组件去实现一个简单…

体验.NET5 RC1极致性能,你也要“卧槽”!

“ 9月14日&#xff0c;.NET5发布了(Release Candidate)RC1版本&#xff0c;是11月正式版本之前两个RC版本中第一个&#xff0c;包含语言新版本C#9和F#5&#xff0c;需要用Visual Studio 2019 (v16.8, Preview 3)才能使用&#xff0c;注意不是Visual Studio 2019&#xff0c;二…

leetcod383. 赎金信

一:题目 二:上码 class Solution { public:bool canConstruct(string ransomNote, string magazine) {unordered_map<char,int>m;for (auto ch:ransomNote) {m[ch];}for (auto ch: magazine) {if(m.find(ch) ! m.end() && m[ch] > 0) m[ch]--;//当magazine中的…

马斯克推崇的第一性原理,究竟有多重要?

职场&认知洞察 丨 作者 / findyi这是findyi公众号的第79篇原创文章最近&#xff0c;第一性原理这个原本离大众很远的物理概念&#xff0c;被媒体炒的火热。第一性原理最开始是由亚里士多德提出来的。他认为任何一个系统都有一个「第一性原理」。他说&#xff1a;“在每个系…

Gartner 组织世界BI大会,PowerBI 与 Tableau 同台竞技

日前&#xff0c;Gartner 组织了全球顶级 BI 产品大会&#xff0c;由于疫情影响&#xff0c;只能远程举办。官方网址如下&#xff1a;https://blogs.gartner.com/rita-sallam/2020/09/11/bi-bake-off-goes-virtual/该会议的原文为&#xff1a;BI Bake-Off&#xff0c;含义为&am…

leetcode15. 三数之和(详解)

一:题目 二:上码 class Solution { public:/**思路:1.这个题出其不意之处在于,其先对这个序列进行了排序排序的好处是1>:如果首元素是大于0的,那么如果无论无何也凑不出02>:同时我们也把相同的元素凑成一块了 可以方便去重了2.接下来,我们就是利用双指针 来处理 数据是否…