,在并发多线程的情况下,为了保证数据安全,我们一般都会对数据进行加锁,通常使用Synchronized或者ReentrantLock同步锁。Synchronized是基于JVM实现的,而ReentrantLock是基于Java代码层面实现的,底层是继承的AQS。AQS的全称是**AbstractQueuedSynchronizer**,即抽象队列同步器,是一个构建锁和同步器的框架。我们常见的并发锁ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier都是基于AQS实现的,所以不懂AQS实现原理就不能说懂Java锁。仔细研究AQS的底层加锁原理,发现和Synchronized的加锁原理惊人的相似。突然想到一句名言,记不得怎么说了,意思是框架的底层原理很相似,大家要多了解底层原理。Synchronized的加锁过程在之前的文章中有详细介绍,没看过的再复习一下。1、synchronized的加锁流程我们先来思考一下Synchronized的加锁需求。如果让你设计Synchronized的对象锁存储结构,你应该怎么设计呢?多个线程执行到Synchronized代码块,只有一个线程获取到锁,然后执行synchronized代码块(需要记录是哪个线程获取了对象锁)。其他线程阻塞(对于阻塞线程,我们可以用链表设计阻塞队列吗?)持有锁的线程调用wait方法,释放锁,等待被唤醒(对于等待线程,我们可以使用设计等待队列的链表??)。被阻塞的线程开始竞争锁,调用notify方法唤醒等待线程。被唤醒的线程进入阻塞队列和竞争锁。以上介绍了Synchronized的加锁过程。Synchronized的对象锁存储结构是不是和我们想的一样?其实是的。下面是对象锁的存储数据结构(C++实现):ObjectMonitor(){_header=NULL;_count=0;_waiters=0,_recursions=0;_object=NULL;_owner=NULL;//持有锁的线程_WaitSet=NULL;//等待队列,存放等待状态的线程_WaitSetLock=0;_Responsible=NULL;_成功=空;_cxq=空;FreeNext=NULL;_EntryList=NULL;//阻塞队列,存放在等待锁块状态线程_SpinFreq=0;_自旋时钟=0;所有者是线程=0;}上图展示了对象锁的基本工作机制:当多个线程同时访问一段同步代码时,会先进入_EntryList队列并阻塞。当一个线程获取到对象的对象锁并进入临界区,将对象锁中的_owner变量设置为当前线程时,就获得了对象锁。如果持有对象锁的线程调用wait()方法,当前持有的对象锁将被释放,_owner变量恢复为null,线程进入_WaitSet集合等待被唤醒。_WaitSet集合中的线程被唤醒,将再次放入_EntryList队列中,重新竞争获取锁。如果当前线程执行完毕,也会释放对象锁并重置变量的值,以便其他线程进入并获取锁。Synchronized对象的锁存储结构和加锁过程和我们想的完全一样。看一下AQS的存储结构和加锁过程,有没有相似之处。2.先分析AQS加锁的原理。我们使用AQS加锁要求:多个线程执行acquire方法时,只有一个线程获取锁,然后执行同步代码块(需要记录是哪个线程获取了对象锁)。其他线程被阻塞(被阻塞的线程,可不可以用链表设计一个阻塞队列?叫“同步队列”?)持有锁的线程调用await方法,释放锁,等待被唤醒(等待线程,难道不能用链表设计等待队列吗?叫“条件队列”?)。被阻塞的线程开始竞争锁,调用signal方法唤醒等待线程。被唤醒的线程进入阻塞队列和竞争锁。AQS的要求和Synchronized完全一样。我们来看看AQS实际的锁机制是如何设计的?它类似于同步吗?AQS的加锁过程并不复杂。只要了解了同步队列和条件队列,以及它们之间的数据流转,就可以完全理解AQS。多个线程竞争AQS锁时,如果有线程获取到锁,则设置owner线程为不竞争锁的线程,阻塞在同步队列中(同步队列采用双向链表和尾插入方式).持有锁的线程调用await方法,释放锁,追加到条件队列的尾部(条件队列采用单链表和尾插入方式)。持有锁的线程调用signal方法,唤醒条件队列的头节点,转移到同步队列的尾部。同步队列的头节点首先获得锁。可以看到AQS和Synchronized的加锁过程几乎是一样的。AQS中的同步队列就是Synchronized中的EntryList,AQS中的条件队列就是Synchronized中的waitSet。两个队列之间的数据传输过程是一样的。3.总结AQS和Synchronized的加锁过程是一样的,都是通过同步队列和条件队列实现的。阻塞状态的线程放入同步队列,等待状态的线程放入条件队列。从条件队列中唤醒的线程被转移到同步队列的尾部,一个竞争锁。看完了AQS的加锁过程,还有不懂AQS的吗?下篇文章会讲AQS加锁的具体源码实现。里面有很多精美的设计,值得借鉴。比如:同步队列为什么要设计成双向链表?而条件队列应该设计成单向链表?为什么AQS锁性能这么好(乐观锁CAS用的)?如何用一个对象实现同步队列和条件队列中的节点?释放锁后,如何唤醒同步队列中的线程?
