AQS基本原理

什么是AQS?

AQS即AbstractQueuedSynchronizer,是一个用于构建锁和同步器的框架。它能降低构建锁和同步器的工作量,还可以避免处理多个位置上发生的竞争问题。在基于AQS构建的同步器中,只可能在一个时刻发生阻塞,从而降低上下文切换的开销,并提高吞吐量。

AQS支持独占锁(exclusive)和共享锁(share)两种模式。

  • 独占锁:只能被一个线程获取到(Reentrantlock)
  • 共享锁:可以被多个线程同时获取(CountDownLatch,ReadWriteLock).

无论是独占锁还是共享锁,本质上都是对AQS内部的一个变量state的获取。state是一个原子的int变量,用来表示锁状态、资源数等。

AQS内部的数据结构与原理

AQS内部实现了两个队列,一个同步队列,一个条件队列。

同步队列的作用是:当线程获取资源失败之后,就进入同步队列的尾部保持自旋等待,不断判断自己是否是链表的头节点,如果是头节点,就不断参试获取资源,获取成功后则退出同步队列。
条件队列是为Lock实现的一个基础同步器,并且一个线程可能会有多个条件队列,只有在使用了Condition才会存在条件队列。

同步队列和条件队列都是由一个个Node组成的。AQS内部有一个静态内部类Node。

static final class Node {static final Node EXCLUSIVE = null;//当前节点由于超时或中断被取消static final int CANCELLED =  1;//表示当前节点的前节点被阻塞static final int SIGNAL    = -1;//当前节点在等待conditionstatic final int CONDITION = -2;//状态需要向后传播static final int PROPAGATE = -3;volatile int waitStatus;volatile Node prev;volatile Node next;volatile Thread thread;Node nextWaiter;final boolean isShared() {return nextWaiter == SHARED;}final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}Node() {    // Used to establish initial head or SHARED marker}Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}}

重要方法的源码解析

//独占模式下获取资源public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

acquire(int arg)首先调用tryAcquire(arg)尝试直接获取资源,如果获取成功,因为与运算的短路性质,就不再执行后面的判断,直接返回。tryAcquire(int arg)的具体实现由子类负责。如果没有直接获取到资源,就将当前线程加入等待队列的尾部,并标记为独占模式,使线程在等待队列中自旋等待获取资源,直到获取资源成功才返回。如果线程在等待的过程中被中断过,就返回true,否则返回false。
如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg)执行过程中被中断过,那么if语句的条件就全部成立,就会执行selfInterrupt();方法。因为在等待队列中自旋状态的线程是不会响应中断的,它会把中断记录下来,如果在自旋时发生过中断,就返回true。然后就会执行selfInterrupt()方法,而这个方法就是简单的中断当前线程Thread.currentThread().interrupt();其作用就是补上在自旋时没有响应的中断。

可以看出在整个方法中,最重要的就是acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

我们首先看Node addWaiter(Node mode)方法,顾名思义,这个方法的作用就是添加一个等待者,根据之前对AQS中数据结构的分析,可以知道,添加等待者就是将该节点加入等待队列.

private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;//尝试快速入队if (pred != null) { //队列已经初始化node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node; //快速入队成功后,就直接返回了}}//快速入队失败,也就是说队列都还每初始化,就使用enqenq(node);return 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;//打断循环}}}}
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();//拿到node的上一个节点//前置节点为head,说明可以尝试获取资源。排队成功后,尝试拿锁if (p == head && tryAcquire(arg)) {setHead(node);//获取成功,更新head节点p.next = null; // help GCfailed = false;return interrupted;}//尝试拿锁失败后,根据条件进行parkif (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}//获取资源失败后,检测并更新等待状态private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)/** 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;}//阻塞当前线程,返回中断状态private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}

具体的boolean tryAcquire(int acquires)实现有所不同。
公平锁的实现如下:

protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}

非公平锁的实现如下:

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) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}

park

在AQS的实现中有一个出现了一个park的概念。park即LockSupport.park().它的作用是阻塞当前线程,并且可以调用LockSupport.unpark(Thread)去停止阻塞。它们的实质都是通过UnSafe类使用了CPU的原语。在AQS中使用park的主要作用是,让排队的线程阻塞掉(停止其自旋,自旋会消耗CPU资源),并在需要的时候,可以方便的唤醒阻塞掉的线程。

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

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

相关文章

ReentrantReadWriteLock源码分析

概述 ReentrantReadWriteLock维护了一对相关的锁&#xff0c;它们分别是共享readLock和独占writeLock。关于共享读锁和排他写锁的概念其实很好理解。所谓共享读锁就是一个线程读的时候&#xff0c;其它线程也可以来读&#xff08;共享&#xff09;&#xff0c;但是不能来写。排…

WinForm 中 comboBox控件之数据绑定

http://www.cnblogs.com/peterzb/archive/2009/05/30/1491923.html 下面介绍三种对comboBox绑定的方式&#xff0c;分别是泛型中IList和Dictionary&#xff0c;还有数据集DataTable 一、IList 现在我们直接创建一个List集合&#xff0c;然后绑定 View Code IList<string>…

MySQL常用引擎有MyISAM和InnoDB区别

MySQL常用引擎有MyISAM和InnoDB&#xff0c;而InnoDB是mysql默认的引擎。MyISAM不支持行锁&#xff0c;而InnoDB支持行锁和表锁。 如何加锁&#xff1f; MyISAM在执行查询语句&#xff08;SELECT&#xff09;前&#xff0c;会自动给涉及的所有表加读锁&#xff0c;在执行更新…

rocketmq 启动mqbroker.cmd闪退

非常奇怪&#xff0c;broker启动闪退&#xff0c;我就摸索了好久&#xff0c;网上各种百度&#xff0c;最后得到正解 将c盘下这个store下的文件全部删除&#xff0c;就可以启动了 猜测是可能mq非正常关闭&#xff0c;导致&#xff0c;具体懂原理的大佬可以来评论区说说

WPF之布局

此文目的旨在让人快速了解&#xff0c;没有什么深度&#xff0c;如需深入了解布局&#xff0c;请参考msdn。 如果你要把WPF当winform使用&#xff0c;拖拖控件也无不可&#xff0c;不过建议还是不要拖的好。 本文将那些用的比较多的几个布局控件&#xff08;Grid、UniformGrid、…

Springboot @Validated和@Valid的区别 及使用

Valid是使用Hibernate validation的时候使用 Validated是只用Spring Validator校验机制使用 说明&#xff1a;java的JSR303声明了Valid这类接口&#xff0c;而Hibernate-validator对其进行了实现 Validation对Valid进行了二次封装&#xff0c;在使用上并没有区别&#xff0c…

【dp】CF17C. Balance

http://codeforces.com/problemset/problem/17/C 题目中给出一个仅含有a,b,c的字符串&#xff0c;已经两种操作每次选出任意两个相邻的字符&#xff0c;用第一个覆盖掉第二个或者反之&#xff0c;最后询问不考虑操作次数&#xff0c;最终有多少种不同的序列其中a&#xff0c;b,…

ECSHOP设置默认配送方式和默认支付方式

用过ECSHOP的站长都知道&#xff0c;首次登陆ECSHOP进行购物的时候&#xff0c;购物流程中没有“默认配送方式和默认支付方式”这个功能 即使网站上只有一种配送方式&#xff0c;它也不会默认选中这个唯一的配送方式。 当你的网站只有一种配送方式&#xff0c;或者&#xff0c;…

spring如何解决循环依赖

什么是循环依赖&#xff1f; 循环依赖其实是指两个及以上bean相互持有对方&#xff0c;最终形成闭环的过程&#xff08;一般聊循环依赖都是默认的单例bean&#xff09;&#xff0c;简单说就是A依赖B,B依赖C,C又依赖A。 下面我就借用别人的网图来解释下&#xff1a; 注意&#…

利用Frame Animation实现动画效果,代码正确,就是达不到变换效果

就是因为把第一帧图片设置成了ImageView的src资源&#xff0c;从而一直覆盖在变换效果之上&#xff0c;去掉ImageView的src属性即可解决。 要想使应用已载入便播放动画效果&#xff0c;直接将 animationDrawables.start(); 放在activity的各种回调函数中&#xff08;onCreate、…

【电信增值业务学习笔记】3 语音类增值业务

作者&#xff1a;gnuhpc 出处&#xff1a;http://www.cnblogs.com/gnuhpc/ 1.一卡多号&#xff1a;&#xff08;Single SIM Multiple Number -SSMN&#xff09; 为拥有一个SIM卡的移动用户提供多个电话号码作为副号码主叫&#xff1a;可以选择用主号码还是副号码发起呼叫被叫&a…

循环依赖源码深度解析

singletonObjects &#xff08;一级缓存&#xff09;它是我们最熟悉的朋友&#xff0c;俗称“单例池”“容器”&#xff0c;缓存创建完成单例Bean的地方。 earlySingletonObjects&#xff08;二级缓存&#xff09;映射Bean的早期引用&#xff0c;也就是说在这个Map里的Bean不是…

多线程间共享变量线程安全问题——ThreadLocal

Java并发编程中很重要的类&#xff1a;ThreadLocal 在多线程应用程序中&#xff0c;对共享变量进行读写的场景是很常见的。如果不使用一定的技术或方案&#xff0c;会引发各种线程安全的问题。常见解决线程安全的方式有synchronized、volatile等方式&#xff0c;但synchronized…

java8流式操作

简介&#xff1a;Stream 中文称为 “流”&#xff0c;通过将集合转换为这么一种叫做 “流” 的元素序列&#xff0c;通过声明性方式&#xff0c;能够对集合中的每个元素进行一系列并行或串行的流水线操作。 操作分类&#xff1a; .stream() stream()把一个源数据&#xff0c;可…

ArrayList源码阅读

private static void extracted() {ArrayList<StudentVO> arrayList new ArrayList<StudentVO>();arrayList.add(new StudentVO("张三", 23));arrayList.add(new StudentVO("李四", 24));arrayList.add(new StudentVO("王五", 24))…

常用的JS小功能整理

<a href"#" onclick "this.style.behaviorurl(#default#homepage);this.sethomepage(http://www.mingrisoft.com)" style" color:Black; font-size: 9pt; font-family: 宋体; text-decoration :none;" >设置主页</a> <a href&quo…

类的加载过程

类的加载过程 代码 public class Father{private int i test();private static int j method();static{System.out.print("(1)");}Father(){System.out.print("(2)");}{System.out.print("(3)");)public int test(){System.out.print("(…

教你如何开发一个 SpringBoot starter

从前从前&#xff0c;有个面试官问我一个 SpringBoot Starter 的开发流程&#xff0c;我说我没有写过 starter&#xff0c;然后就没有然后了&#xff0c;面试官说我技术深度不够。 我想说这东西不是很简单吗&#xff0c;如果要自己写一个出来也是分分钟的事情。至于就因为我没…

两分钟彻底让你明白Android Activity生命周期(图文)!

转&#xff1a;http://blog.csdn.net/qyf_5445/article/details/8290232 首先看一下Android api中所提供的Activity生命周期图(不明白的&#xff0c;可以看完整篇文章&#xff0c;在回头看一下这个图&#xff0c;你会明白的): Activity其实是继承了ApplicationContext这个类&am…

spring4和spring5的aop执行顺序区别?

spring4单切面 spring4多切面 spring4 spring5