在并发多线程的情况下,为了保证数据安全性 , 一般我们会对数据进行加锁 , 通常使用Synchronized或者ReentrantLock同步锁 。Synchronized是基于JVM实现 , 而ReentrantLock是基于Java代码层面实现的 , 底层是继承的AQS 。
AQS全称AbstractQueuedSynchronizer
,即抽象队列同步器,是一种用来构建锁和同步器的框架 。
我们常见的并发锁ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier都是基于AQS实现的,所以说不懂AQS实现原理的,就不能说了解Java锁 。
当我仔细研究AQS底层加锁原理,发现竟然跟Synchronized加锁原理有惊人的相似 。让我突然想到一句名言,记不清怎么说了,意思是框架底层原理很相似,大家多学习底层原理 。
Synchronized的加锁流程在前几篇文章已经详细讲过,没看过一块再温习一下 。
1. Synchronized加锁流程我们先想一下Synchronized的加锁需求,如果让你设计Synchronized的对象锁存储结构 , 该怎么设计?
- 多个线程执行到Synchronized代码块,只有一个线程获取锁,然后执行同步代码块(需要记录哪个线程获取了对象锁) 。
- 其他线程被阻塞(被阻塞的线程,是不是可以用链表设计个阻塞队列?)
- 持有锁的线程调用wait方法,释放锁,等待被唤醒(等待的线程,是不是可以用链表设计个等待队列?) 。
- 被阻塞的线程开始竞争锁
- 调用notify方法,唤醒等待的线程,被唤醒的线程进入阻塞队列,一块竞争锁 。
下面是对象锁的存储数据结构(由C++实现):
ObjectMonitor() {_header= NULL;_count= 0;_waiters= 0,_recursions= 0;_object= NULL;_owner= NULL; // 持有锁的线程_WaitSet= NULL; // 等待队列,存储处于wait状态的线程_WaitSetLock= 0 ;_Responsible= NULL ;_succ= NULL ;_cxq= NULL ;FreeNext= NULL ;_EntryList= NULL ; // 阻塞队列,存储处于等待锁block状态的线程_SpinFreq= 0 ;_SpinClock= 0 ;OwnerIsThread = 0 ;}
![重大发现,AQS加锁机制竟然跟Synchronized有惊人的相似](http://img.zhejianglong.com/231019/1S42K5I-0.png)
文章插图
上图展示了对象锁的基本工作机制:
- 当多个线程同时访问一段同步代码时,首先会进入 _EntryList队列中阻塞 。
- 当某个线程获取到对象的对象锁后进入临界区域,并把对象锁中的 _owner变量设置为当前线程 , 即获得对象锁 。
- 若持有对象锁的线程调用 wait() 方法,将释放当前持有的对象锁,_owner变量恢复为null,同时该线程进入 _WaitSet 集合中等待被唤醒 。
- 在_WaitSet集合中的线程被唤醒,会被再次放到_EntryList队列中,重新竞争获取锁 。
- 若当前线程执行完毕也将释放对象锁并复位变量的值,以便其他线程进入获取锁 。
再看一下AQS的存储结构和加锁流程 , 有没有相似的地方 。
2. AQS加锁原理先分析一下,我们使用AQS的加锁需求:
- 多个线程执行到acquire方法的时候,只有一个线程获取锁 , 然后执行同步代码块(需要记录哪个线程获取了对象锁) 。
- 其他线程被阻塞(被阻塞的线程,是不是可以用链表设计个阻塞队列?名叫”同步队列“?)
- 持有锁的线程调用await方法,释放锁,等待被唤醒(等待的线程,是不是可以用链表设计个等待队列?名叫”条件队列“?) 。
- 被阻塞的线程开始竞争锁
- 调用signal方法,唤醒等待的线程,被唤醒的线程进入阻塞队列,一块竞争锁 。
我们再看一下AQS实际的加锁机制是怎么设计的?是不是跟Synchronized相似?
![重大发现,AQS加锁机制竟然跟Synchronized有惊人的相似](http://img.zhejianglong.com/231019/1S42L458-1.jpg)
文章插图
AQS的加锁流程并不复杂,只要理解了同步队列和条件队列,以及它们之间的数据流转,就算彻底理解了AQS 。
- 当多个线程竞争AQS锁时,如果有个线程获取到锁,就把ower线程设置为自己
- 没有竞争到锁的线程 , 在同步队列中阻塞(同步队列采用双向链表,尾插法) 。
- 持有锁的线程调用await方法,释放锁 , 追加到条件队列的末尾(条件队列采用单链表,尾插法) 。
- 持有锁的线程调用signal方法,唤醒条件队列的头节点,并转移到同步队列的末尾 。
- 同步队列的头节点优先获取到锁
推荐阅读
- 详解AQS中的condition源码原理
- 6 Java多线程:锁与AQS(下)
- 数据科学学习手札146 geopandas中拓扑非法问题的发现、诊断与修复
- 鹅长微服务发现与治理巨作PolarisMesh实践-上
- JUC中的AQS底层详细超详解
- ULID规范解读与实现原理
- 小米11系列出现重大翻车_小米11系列是不是真的翻车了
- 微信怎么强行删除好友(微信怎么删除好友不要被发现)
- 深入理解AQS--jdk层面管程实现【管程详解的补充】
- 俄乌战局迎重大变化?来看新进展「俄乌最新战况」