那什么是管程呢?所谓管程,就是 管理共享变量以及对共享变量操作的过程 ,其有三种模型,分别为 hasen 模型、hoare 模型和 mesa 模型。目前应用最广泛的是mesa模型,而java采用的也是这种mesa模型(其模型图如下图所示):
可能这个图大家现在还看不太明白,没关系,暂时留个印象,当看完指北君aqs系列文章以后,你再回过头来看这个图,肯定秒懂!
java中的synchronized关键字就是其管程的具体实现,当然,今天所要聊的aqs同样也是。aqs是java并发编程的基础,只要掌握它,java.util .concurrent工具包下的大部分工具类源码你都能在10分钟内看懂,连源码都懂了,还怕不知道怎么用吗?!
所以,针对aqs,指北君将用三篇文章来讲解:
第一篇即本篇,我会将aqs做个整体的介绍,并将其基础总结成了三把斧头,有了这三把斧头,你就能快速斩获aqs
第二篇我们则讲aqs如何通过锁机制解决互斥问题
第三篇则是aqs如何通过条件变量来解决线程通信协作问题
好了,现在随着指北君开始第一篇的学习吧
aqs是什么好了,本文的正篇正式开始。前言说了半天aqs,aqs到底是什么呢?aqs全称为abstractqueuedsynchronizer,翻译成中文即:抽象队列同步器,它是java.util .concurrent工具包下的抽象类,也是一个模板类(设计模式中的模板模式可以了解一波),你也可以理解为为开发人员提供的一种同步框架,它已经帮我们实现了大部分公共通用逻辑,如线程入队、出队,阻塞、唤醒等,我们只需要根据我们自己的需求,实现一些特定的方法,这些方法也叫钩子方法,比如下面几个方法:
tryacquire:独占模式下获取同步状态tryrelease:独占模式下释放同步状态tryacquireshared:共享模式下获取同步状态tryreleaseshared:共享模式下释放同步状态isheldexclusively:独占模式下,查看当前线程是否获取同步状态aqs针对互斥,提供了两种模式,即独占模式和共享模式。独占模式只允许一个线程拿到锁去操作共享资源,而共享锁则有多把锁,允许多个线程同时操作共享资源,这个第二篇会详细讲解,在这之前我们需要先了解aqs的三板斧,这三个是了解aqs的基础:
状态:aqs中的所有逻辑都是依据状态state来进行的,所以它是整个类的和兴。它是被关键字volatile修饰的,保证其可见性和部分有序性队列:一共有两种队列,同步队列和条件队列。当线程的请求在短时间内得不到满足时,线程会被包装成某种类型的数据结构放入队列中,当条件满足时则会拿出队列。cas:由unsafe工具类来实现的,其操作具有原子性,aqs通过cas和volatile来保证状态的线程安全第一板斧:状态private volatile int state;状态是被volatile修饰的int类型变量,它的值代表着锁还剩多少。在独占锁模式下,只有一把锁,则state只有0和1两个值,1代表锁没被其他线程占有,目前可获取;0则代表锁已经被其他线程占有了。在共享锁模式下,state最大值就是锁的数量。
后面我们对state的修改都是通过cas操作,所以是线程安全的。
第二板斧:队列aqs中存在两种队列,一种叫同步队列,它是双向链表,其获取锁失败的线程会被包装成node放入同步队列中;另一种叫条件队列,它是单向链表,线程可以调用等待方法来把自己放入条件队列,或者调用唤醒方法把条件队列的其他被包装成node的线程移到同步队列中。这个大家如果此时看不太懂没关系,第三篇文章会详细介绍这个等待唤醒机制。我们来看看线程包装成node的node类:
static final class node { // nextwaiter属性的具体值 static final node shared = new node(); static final node exclusive = null; // 锁的状态 static final int cancelled = 1; static final int signal = -1; static final int condition = -2; static final int propagate = -3; // 线程所处的等待锁的状态:cancelled,signal,condition,propagate,初始化时,该值为0 volatile int waitstatus; // 双向链表前指针和后指针 volatile node prev; volatile node next; // 表示当前node所代表的thread volatile thread thread; // 如果此属性为exclusive,则为独占模式;为shared,则为共享模式 node nextwaiter; // 判断是否是共享模式 final boolean isshared() { return nextwaiter == shared; } // 获取链表的头个节点 final node predecessor() throws nullpointerexception { node p = prev; if (p == null) throw new nullpointerexception(); else return p; } // 构造函数,一般用在创建head头结点时使用 node() { } // 构造函数,在addwaiter方法时使用 node(thread thread, node mode) { this.nextwaiter = mode; this.thread = thread; } // 构造函数,在condition中使用 node(thread thread, int waitstatus) { this.waitstatus = waitstatus; this.thread = thread; }}指北君已经在源码上做了详细的注释,但这几个关键的属性上还是提出来单独看看。
下面四个属性是和节点相关的:
prev:双向链表的前驱节点next:双向链表的后继节点thread:节点所代表的线程waitstatus:该节点线程所处的状态,即等待锁的状态下面四个属性是waitstatus的具体状态:
cancelled:此节点的线程被取消了signal:此节点的后继节点线程被挂起,需要被唤醒condition:此节点的线程在等待信号,也表明当前节点不在同步队列中,而在条件队列中propagate:此节点下一个acquireshared应该无条件传播关于waitstatus有几个点需要注意下:
waitstatus除了上面四个状态,还有一个隐式的状态为0,即在node初始化的时候在独占锁模式下,只会有到状态cancelled和signal。需要特别注意的是,signal它代表的不是自己线程的状态,而是它后继节点的状态,当一个节点waitstatus为signal时,意味着此节点的后继节点被挂起,当此节点释放锁或者被取消拿锁,应该要唤醒后继节点在共享锁模式下,只会用到状态cancelled和propagate第三板斧:cascas又叫比较交换操作,它是unsafe类中compareandswapxxx方法,我通过下面的例子做个简单的介绍:
unsafe.compareandswapobject(this, tailoffset, expect, update);cas执行时,会拿地址tailoffset上的值与expect做比较,如果相同,则会将地址上的值更新为update,并返回true,否则直接返回false。
了解了cas的基本例子后,我们看下aqs中cas相关的代码:
private static final unsafe unsafe = unsafe.getunsafe();// state、head、tail,waitstatus、next的偏移量private static final long stateoffset;private static final long headoffset;private static final long tailoffset;private static final long waitstatusoffset;private static final long nextoffset;// 静态代码块,初始化五个偏移量static { try { stateoffset = unsafe.objectfieldoffset (abstractqueuedsynchronizer.class.getdeclaredfield(state)); headoffset = unsafe.objectfieldoffset (abstractqueuedsynchronizer.class.getdeclaredfield(head)); tailoffset = unsafe.objectfieldoffset (abstractqueuedsynchronizer.class.getdeclaredfield(tail)); waitstatusoffset = unsafe.objectfieldoffset (node.class.getdeclaredfield(waitstatus)); nextoffset = unsafe.objectfieldoffset (node.class.getdeclaredfield(next)); } catch (exception ex) { throw new error(ex); }} // 下面5个方法都是cas操作了// cas操作 stateprotected final boolean compareandsetstate(int expect, int update) { return unsafe.compareandswapint(this, stateoffset, expect, update);}// cas操作 headprivate final boolean compareandsethead(node update) { return unsafe.compareandswapobject(this, headoffset, null, update);} // cas操作 tailprivate final boolean compareandsettail(node expect, node update) { return unsafe.compareandswapobject(this, tailoffset, expect, update);} // cas操作 waitstatusprivate static final boolean compareandsetwaitstatus(node node, int expect, int update) { return unsafe.compareandswapint(node, waitstatusoffset, expect, update);}// cas操作 nextoffsetprivate static final boolean compareandsetnext(node node, node expect, node update) { return unsafe.compareandswapobject(node, nextoffset, expect, update);}我们说第二板斧的时候说过,其五个属性state、head、tail,waitstatus、next都是被volatile修饰的,所以cas对其操作能保证其线程安全,因此也可以猜到这些属性肯定是多线程争着修改的目标。静态块里则是对这五个属性偏移量进行初始化。
总结aqs的三板斧就介绍完啦,我们再来简单回顾下:
aqs(abstractqueuedsynchronizer)是java.util .concurrent工具包下的抽象类,它通过实现mesa管程来解决并发领域中的同步与互斥问题。aqs实现中最重要的三点就是状态、cas和队列,我们也称之为aqs的三板斧。aqs的一切操作都是依据状态state来的,它是被volatile修饰的全局变量,因此我们通过cas操作使其线程安全。队列是维护阻塞等待线程的容器,所有未获得锁或被要求等待的线程都会被包装成node放入队列中。
智慧城市的到来我们可以拥有什么
想要分析网络变更会有什么影响
英特尔宣布放弃NUC业务!
浅谈服务机器人
中国芯片最新50强榜单发布!
AQS是什么
金立、魅族,国产手机品牌闪耀MWC
如何用电饭锅火力调节器制作一个恒温电烙铁?
美国已停止为中国最大的LED芯片制造厂商三安光电提供产品和服务
Altera面向OpenCL的SDK是FPGA业界首个实现Khronos标准
高速数字电路如何中抑制噪声,高频部分受封装影响
【MXR·动态】梦想人受邀参加东莞“元宇宙——工业AR”助力企业数字化转型主题沙龙
普通HDMI线和光纤HDMI线有什么不同之处
海尔发布全球首个智能制造云平台
妙联宝65W PD充电器:一款带充电器的拓展坞
2023 SENSOR CHINA重磅重启!9月13-15日,深圳市工采网邀您共聚传感产业盛会!
中国联通王晓初:实现地市以上城市主城区 5G 网络基本覆盖
LoRa在中国市场迎来了全面开花的时代
物联网的无线通信技术:NB-IoT、LoRa简述
小米发布新版本小米笔记本Air,配置升级 价格也升了1000元!