重大发现,AQS加锁机制竟然跟Synchronized有惊人的相似(aqs如何实现可重入锁)

  本篇文章为你整理了重大发现,AQS加锁机制竟然跟Synchronized有惊人的相似(aqs如何实现可重入锁)的详细内容,包含有基于aqs的锁有哪些 aqs如何实现可重入锁 aqs synchronized aqs实现锁 重大发现,AQS加锁机制竟然跟Synchronized有惊人的相似,希望能帮助你了解 重大发现,AQS加锁机制竟然跟Synchronized有惊人的相似。

   在并发多线程的情况下,为了保证数据安全性,一般我们会对数据进行加锁,通常使用Synchronized或者ReentrantLock同步锁。Synchronized是基于JVM实现,而ReentrantLock是基于Java代码层面实现的,底层是继承的AQS。

  AQS全称AbstractQueuedSynchronizer,即抽象队列同步器,是一种用来构建锁和同步器的框架。

  
在并发多线程的情况下,为了保证数据安全性,一般我们会对数据进行加锁,通常使用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方法,唤醒等待的线程,被唤醒的线程进入阻塞队列,一块竞争锁。

  上面描述了Synchronized的加锁流程,Synchronized的对象锁存储结构是不是跟咱们想的一样?实际就是的。

  下面是对象锁的存储数据结构(由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 ;

  

 

  上图展示了对象锁的基本工作机制:

  
当某个线程获取到对象的对象锁后进入临界区域,并把对象锁中的 _owner变量设置为当前线程,即获得对象锁。

  
若持有对象锁的线程调用 wait() 方法,将释放当前持有的对象锁,_owner变量恢复为null,同时该线程进入 _WaitSet 集合中等待被唤醒。

  
Synchronized对象锁存储结构和加锁流程,竟然跟咱们想的一样。

  再看一下AQS的存储结构和加锁流程,有没有相似的地方。

  2. AQS加锁原理

  先分析一下,我们使用AQS的加锁需求:

  多个线程执行到acquire方法的时候,只有一个线程获取锁,然后执行同步代码块(需要记录哪个线程获取了对象锁)。

  其他线程被阻塞(被阻塞的线程,是不是可以用链表设计个阻塞队列?名叫”同步队列“?)

  持有锁的线程调用await方法,释放锁,等待被唤醒(等待的线程,是不是可以用链表设计个等待队列?名叫”条件队列“?)。

  被阻塞的线程开始竞争锁

  调用signal方法,唤醒等待的线程,被唤醒的线程进入阻塞队列,一块竞争锁。

  AQS的需求跟Synchronized一模一样。

  我们再看一下AQS实际的加锁机制是怎么设计的?是不是跟Synchronized相似?

  AQS的加锁流程并不复杂,只要理解了同步队列和条件队列,以及它们之间的数据流转,就算彻底理解了AQS。

  当多个线程竞争AQS锁时,如果有个线程获取到锁,就把ower线程设置为自己

  没有竞争到锁的线程,在同步队列中阻塞(同步队列采用双向链表,尾插法)。

  持有锁的线程调用await方法,释放锁,追加到条件队列的末尾(条件队列采用单链表,尾插法)。

  持有锁的线程调用signal方法,唤醒条件队列的头节点,并转移到同步队列的末尾。

  同步队列的头节点优先获取到锁

  可以看到AQS和Synchronized的加锁流程几乎是一模一样的,AQS中同步队列就是Synchronized中EntryList,AQS中条件队列就是Synchronized中的waitSet,两个队列之间的数据转移流程也是一样的。

  3. 总结

  AQS跟Synchronized的加锁流程是一样的,都是通过同步队列和条件队列实现的,阻塞状态的线程被放到同步队列中,等待状态的线程被放到条件队列中,从条件队列唤醒的线程又被转移到同步队列末尾,一块竞争锁。

  看完AQS加锁流程,还没有人不懂AQS的?

  下篇文章再讲一下AQS加锁具体的源码实现。里面有很多精巧的设计,值得我们学习。

  比如:

  为什么同步队列要设计成双向链表?而条件队列要设计成单链表?

  为什么AQS加锁性能这么好(乐观锁CAS使用)?

  同步队列和条件队列中节点怎么用一个对象实现?

  释放锁后,怎么唤醒同步队列中线程?

  我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见

  以上就是重大发现,AQS加锁机制竟然跟Synchronized有惊人的相似(aqs如何实现可重入锁)的详细内容,想要了解更多 重大发现,AQS加锁机制竟然跟Synchronized有惊人的相似的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: