欢迎关注我的公众号:石杉的架构笔记(ID:shishan100)
java
周一至周五早8点半!精品技术文章准时送上!面试
上一篇文章聊了一下java并发中经常使用的原子类的原理和Java 8的优化,具体请参见文章:大白话聊聊Java并发面试问题之Java 8如何优化CAS性能?。算法
这篇文章,咱们来聊聊面试的时候比较有杀伤力的一个问题:聊聊你对AQS的理解?性能优化
以前有同窗反馈,去互联网公司面试,面试官聊到并发时就问到了这个问题。当时那位同窗心里估计受到了一万点伤害。。。架构
由于首先,不少人还真的连AQS是什么都不知道,可能听都没据说过。或者有的人据说过AQS这个名词,可是可能连具体全称怎么拼写都不知道。并发
更有甚者,可能会说:AQS?是否是一种思想?咱们平时开发怎么来用AQS?分布式
整体来讲,不少同窗估计都对AQS有一种云里雾里的感受,若是用搜索引擎查一下AQS是什么?看几篇文章,估计就直接放弃了,由于密密麻麻的文字,实在是看不懂!微服务
因此,基于上述痛点,我们这篇文章,就用最简单的大白话配合N多张手绘图,给你们讲清楚AQS究竟是什么?让各位同窗面试被问到这个问题时,不至于不知所措。高并发
首先咱们来看看,若是用java并发包下的ReentrantLock来加锁和释放锁,是个什么样的感受?oop
这个基本学过java的同窗应该都会吧,毕竟这个是java并发基本API的使用,应该每一个人都是学过的,因此咱们直接看一下代码就行了:
上面那段代码应该不难理解吧,无非就是搞一个Lock对象,而后加锁和释放锁。
你这时可能会问,这个跟AQS有啥关系?关系大了去了!由于java并发包下不少API都是基于AQS来实现的加锁和释放锁等功能的,AQS是java并发包的基础类。
举个例子,好比说ReentrantLock、ReentrantReadWriteLock底层都是基于AQS来实现的。
那么AQS的全称是什么呢?AbstractQueuedSynchronizer,抽象队列同步器。给你们画一个图先,看一下ReentrantLock和AQS之间的关系。
咱们来看上面的图。说白了,ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer类型的对象。这个AQS对象就是ReentrantLock能够实现加锁和释放锁的关键性的核心组件。
好了,那么如今若是有一个线程过来尝试用ReentrantLock的lock()方法进行加锁,会发生什么事情呢?
很简单,这个AQS对象内部有一个核心的变量叫作state,是int类型的,表明了加锁的状态。初始状态下,这个state的值是0。
另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪一个线程,初始化状态下,这个变量是null。
接着线程1跑过来调用ReentrantLock的lock()方法尝试进行加锁,这个加锁的过程,直接就是用CAS操做将state值从0变为1。
若是不知道CAS是啥的,请看上篇文章,大白话聊聊Java并发面试问题之Java 8如何优化CAS性能?
若是以前没人加过锁,那么state的值确定是0,此时线程1就能够加锁成功。
一旦线程1加锁成功了以后,就能够设置当前加锁线程是本身。因此你们看下面的图,就是线程1跑过来加锁的一个过程。
其实看到这儿,你们应该对所谓的AQS有感受了。说白了,就是并发包里的一个核心组件,里面有state变量、加锁线程变量等核心的东西,维护了加锁状态。
你会发现,ReentrantLock这种东西只是一个外层的API,内核中的锁机制实现都是依赖AQS组件的。
这个ReentrantLock之因此用Reentrant打头,意思就是他是一个可重入锁。
可重入锁的意思,就是你能够对一个ReentrantLock对象屡次执行lock()加锁和unlock()释放锁,也就是能够对一个锁加屡次,叫作可重入加锁。
你们看明白了那个state变量以后,就知道了如何进行可重入加锁!
其实每次线程1可重入加锁一次,会判断一下当前加锁线程就是本身,那么他本身就能够可重入屡次加锁,每次加锁就是把state的值给累加1,别的没啥变化。
接着,若是线程1加锁了以后,线程2跑过来加锁会怎么样呢?
咱们来看看锁的互斥是如何实现的?线程2跑过来一下看到,哎呀!state的值不是0啊?因此CAS操做将state从0变为1的过程会失败,由于state的值当前为1,说明已经有人加锁了!
接着线程2会看一下,是否是本身以前加的锁啊?固然不是了,“加锁线程”这个变量明确记录了是线程1占用了这个锁,因此线程2此时就是加锁失败。
给你们来一张图,一块儿来感觉一下这个过程:
接着,线程2会将本身放入AQS中的一个等待队列,由于本身尝试加锁失败了,此时就要将本身放入队列中来等待,等待线程1释放锁以后,本身就能够从新尝试加锁了
因此你们能够看到,AQS是如此的核心!AQS内部还有一个等待队列,专门放那些加锁失败的线程!
一样,给你们来一张图,一块儿感觉一下:
接着,线程1在执行完本身的业务逻辑代码以后,就会释放锁!他释放锁的过程很是的简单,就是将AQS内的state变量的值递减1,若是state值为0,则完全释放锁,会将“加锁线程”变量也设置为null!
整个过程,参见下图:
接下来,会从等待队列的队头唤醒线程2从新尝试加锁。
好!线程2如今就从新尝试加锁,这时仍是用CAS操做将state从0变为1,此时就会成功,成功以后表明加锁成功,就会将state设置为1。
此外,还要把“加锁线程”设置为线程2本身,同时线程2本身就从等待队列中出队了。
最后再来一张图,你们来看看这个过程。
OK,本文到这里为止,基本借着ReentrantLock的加锁和释放锁的过程,给你们讲清楚了其底层依赖的AQS的核心原理。
基本上你们把这篇文章看懂,之后不再会担忧面试的时候被问到:谈谈你对AQS的理解这种问题了。
其实一句话总结AQS就是一个并发包的基础组件,用来实现各类锁,各类同步组件的。它包含了state变量、加锁线程、等待队列等并发中的核心组件。
并发系列文章,正在更新中,欢迎关注:
大白话聊聊Java并发面试问题之volatile究竟是什么?
大白话聊聊Java并发面试问题之Java 8如何优化CAS性能?
大白话聊聊Java并发面试问题之谈谈你对AQS的理解?
大白话聊聊Java并发面试问题之公平锁与非公平锁是啥? 敬请期待
大白话聊聊Java并发面试问题之微服务注册中心的读写锁优化? 敬请期待
若有收获,请帮忙转发,您的鼓励是做者最大的动力,谢谢!
一大波微服务、分布式、高并发、高可用的原创系列文章正在路上
欢迎扫描下方二维码,持续关注:
石杉的架构笔记(id:shishan100)
十余年BAT架构经验倾囊相授
推荐阅读:二、【双11狂欢的背后】微服务注册中心如何承载大型系统的千万级访问?
三、【性能优化之道】每秒上万并发下的Spring Cloud参数优化实战
六、大规模集群下Hadoop NameNode如何承载每秒上千次的高并发访问
七、【性能优化的秘密】Hadoop如何将TB级大文件的上传性能优化上百倍
八、拜托,面试请不要再问我TCC分布式事务的实现原理坑爹呀!
九、【坑爹呀!】最终一致性分布式事务如何保障实际生产中99.99%高可用?
十一、【眼前一亮!】看Hadoop底层算法如何优雅的将大规模集群性能提高10倍以上?
1六、亿级流量系统架构之如何设计全链路99.99%高可用架构