面试系列之AQS以及实现
- AQS:
-
特性:阻塞等待队列,共享/独占,公平/非公平,可重入,可中断
-
AQS原理:AQS基于管程的MESA模型实现,依然存在同步队列和条件队列
-
过程分析:
非公平锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
公平锁
final void lock() {
acquire(1);
}
加锁过程
第一个线程进来,此时锁没有被获取,所以肯定能获取锁
在线程1持有锁的情况下,线程2进入
```java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(arg)
addWaiter(Node.EXCLUSIVE) 不得不开始排队
Thread3进入后重复上述过程入队,上述过程涉及到的队列都属于同步队列
解锁过程
重点是在抢锁失败的情况下,采用两个for循环,保证入队成功,保证抢锁成功。
AQS的是一个同步框架,它定义了入队和出队的过程。需要具体的实现来决定加锁逻辑以及解锁逻辑。
AQS的实现类:
-
ReentrantLock:独占锁,可以定义为公平锁/非公平锁 可重入。 公平锁和非公平锁的区别在于于是否直接进行cas的抢锁然后再去排队排队,公平锁一般情况下性能会更好,因为可以减少线程的唤醒和阻塞。 解锁时唤醒头结点的下一个节点
-
Semaphore:(可以用于限流)共享锁,共享锁会先设置state为我们定义的资源数量,只要state大于0我们就可以获取锁,每次获取减少相应的信号量。在解锁阶段和独占锁相比,在唤醒一个节点成功抢锁以后然后会马上唤醒它之后的节点,因为共享锁认为每次都会有足够多的资源,就不用等到下个线程来唤醒自己,增加性能。
-
ReentrantReadWriteLock: 读写锁,读锁采用共享锁,写锁采用独占锁,设计点在于使用一个变量的高低位分别表示读锁和写锁的状态,并且使用ThreadLocal记录共享锁的重入次数
-
AQS 实现了同步队列的入队与出队操作,condition定义了条件变量以及条件队列
在API层面的管程MESA模型的实现----reentrantLock+condition
此处于synchronize对比讲解
-
synchronize基于monitor对象实现是jvm层面的锁
-
ReentrantLock 基于AQS实现了独占锁,其定义了一个共享代码块,以及一个同步队列。其同步对列的入队与出队操作如上所述,本次下面重点描述条件变量
-
当一个线程进入同步代码块以后,调用条件变量(condition)的await方法,就会进入条件队列并且释放锁然后调用LockSpuer的park方法进入阻塞,这是await方法的前半部分作用
-
-
然后需要其他抢到锁的线程调用条件变量(condition)的signalAll将条件队列单向链表的所有线程加到同步队列双向链表的最后,并且会将node的WaitStatus置为0,然后执行和独占锁一样的入队逻辑。
-
当这个线程在同步队列中在此被唤醒时(也就是我们await方法的后半段)
值得注意的是AQS每次在调价转同步都是放在同步队列的末尾,而Synchronize在默认策略下条件转同步都是放在队列的前面,也就是一个采用尾插发,一个采用头插法
此处学习完全可以对比synchronize一起学习。对比synchronize的moniter对象进行学习,可以加深两种锁的学习,虽然两种锁在是完全不同的两个层面,但是设计思想确实相同的,都是采用管程的MESA模型