一、什么是AQS
全称是 AbstractQueuedSynchronizer(队列同步器,下文简称同步器),是阻塞式锁和相关的同步器工具的框架,它是构建锁或者其他同步组件的基础框架。【AQS是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的含义】
AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,重写同步器指定的方法时,AQS提供了3个方法访问或修改同步状态。
- getState():获取当前同步状态
- setState(int newState):设置当前同步状态
- compareAndSetState(int expect,int update):使用CAS设置当前状态,保证状态设置的原子性
节点是构成同步队列的基础,同步器拥有首节点(head)和尾结点(tail),没有成功获取同步状态的线程将会成为节点加入该队列的尾部。同步器包含两类节点类型的引用:一个指向头节点,另一个指向尾结点。
同步器提供了一个基于CAS的设置尾结点的方法:compareAndSetState(int expect,int update),它需要传递当前线程“认为”的尾结点和当前节点,只有设置成功,当前节点才与之前的尾结点建立联系。
设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能成功获取到同步状态,因此,设置头节点不需要用CAS,只需要将首节点设置为原首节点的后继节点并断开原首节点的next引用即可。
AQS常见的实现类
- ReentrantLock 阻塞式锁
- Semaphore 信号量
- CountDownLatch 倒计时锁
- 新的线程与队列中的线程共同来抢资源,是非公平锁
- 新的线程到队列中等待,只让队列中的head线程获取锁,是公平锁
比较典型的AQS实现类ReentrantLock,它默认就是非公平锁,新的线程与队
列中的线程共同来抢资源
二、ReentrantLock的基本概念
RenntrantLock实现了Lock接口,是一个可重入且独占式的锁。它更灵活、更强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。
ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似。构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。
ReentrantLock里面有一个内部类 sync,它 继承 AQS,添加锁和释放锁的大部分操作实际上都是在 sync中实现的。sync有公平锁fairSync和非公平锁 NonfairSync两个子类。
公平锁 : 锁被释放之后,先申请的线程先得到锁。性能较差一些,因为公平锁为了保证时间上的绝对顺序,上下文切换更频繁。
非公平锁:锁被释放之后,后申请的线程可能会先获取到锁,是随机或者按照其他优先级排序的。性能更好,但可能会导致某些线程永远无法获取到锁。
三、与Synchronized的对比
相同点:二者都是加锁方式同步,并且都是阻塞式同步。即如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的。
区别:synchronized是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock是jdk1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try、finally语句块来实现。
synchronized经过编译,会在同步块的前后分别形成monitorenter和monitoreExit这两个字节码指令。在执行前者指令时首先要尝试获取对象锁。如果这个锁没有被锁定,或者当前线程已经有了对象锁,就把锁的计算器加1,相应的执行后者时,将计算器减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。
ReentrantLock是java.util.concurrent包下提供的一套互斥锁,有以下高级功能:
1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于synchronized来说可以避免死锁。
2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不好。
3.锁绑定多个条件,RenntrantLock对象可以同时绑定多个对象。