
JUC-05_AQS和ReentrantLock
AQS
AbstractQueuedSynchronizer :抽象同步队列,定义一个基于FIFO的队列,提供给其他锁和同步器的独占或实现的基础
-
本身
-
继承了AbstractOwnableSynchronizer:提供:设置当前独占线程,获取当前独占线程的方法
-
实现Serializable序列化接口
-
-
内部有
-
volatile的state字段,记录同步状态,通过cas和final保证并发安全,get set caset
-
内部类Node,多个Node形成虚拟的双向队列,放线程(阻塞队列)
-
内部类ConditionObject,实现Condition接口,放等待线程的队列(条件队列),可以new多个条件,await和signal
-
提供的模板方法,默认抛出UnsupportedOperationException,其他方法为final
-
tryAcquire(int i):尝试加独占锁
-
tryRelease(int i):尝试释放独占锁
-
tryAcquireShared(int i):尝试加共享锁
-
tryReleaseShared(int i):尝试释放共享锁
-
isHeldExclusively:该线程是否正在独占资源
-
独占锁获取释放流程:
-
AQS内的每个Node节点都用一个状态值来记录该节点状态,且通过前驱后继将节点形成一个双向链表。
-
AQS自己保存了头节点和尾节点的位置,且提供CAS入队出队的操作。
-
Node中提供前驱后继,获取上一个,获取下一个Node的方法。
-
当获得锁时,获得锁的线程进入同步队列的队头,
-
此时其他进入的线程CAS操作追加到阻塞队列的队尾也就是Node中。node状态为阻塞,LockSupprt.park进入自旋阻塞。
-
每个阻塞node判断,当自己的前驱是头节点时才可尝试获取同步状态。
-
当锁释放tryRelease时,头节点的node出队或者阻塞node发生中断时,才会被唤醒。

**总结:**在获取同步状态时,同步器维护一个同步队列,获取到同步状态的线程为队头,其他没获取到同步状态的线程前驱后继连接起来形成同步队列,每个阻塞的node自旋判断,当自己的头节点是队头时才可以尝试获取同步状态。
同步队列调用tryRelease方法,队头的线程释放同步状态,出队并通知自己的后继节点。
共享锁获取释放流程:
共享锁的特点在于其他线程可以同时共享 读取这个操作,但是写入操作还是阻塞的。也是和独占锁的最大区别
- (一般读写锁 读是共享,写是互斥,提高并发)
-
在获取同步状态cas操作时,只要状态值>0即可获得共享状态
-
在释放同步状态时,会唤醒后续处于等待状态的节点。
ConditionObject
-
AQS的内部类,实现了Condition接口,主要目的(AQS中)是对线程进行等待-通知操作
-
(个人见解:如果把AQS比作java中通过CAS实现的一个monitor,那conditionObject的目的就是等待队列放入同步队列)
-
Condition:提供await()和signal()方法,来对线程进行等待-通知操作
-
这里就不再对它进行展开了,只需知道它是AQS中Condition的实现
-
复用AQS的内部类Node
-
Node状态为Condition和Canceled
-
调用await会创建一个condition类型的node并入队
-
调用signal会把队头的node通过cas操作入到同步队列中
-
signalAll把条件队列全部挪到同步队列中
总结AQS:感觉就像是另一个管程monitor,期间的CAS操作,同步队列和等待队列实现比较巧妙。
难怪说AQS是juc下Lock和Future的核心,而Unsafe和LockSupport是AQS的核心。
-
Unsafe提供cas原子操作,保证同步状态的原子性
-
LockSupport提供等待-通知机制,灵活可控的线程调度
-
AQS
-
cas和volatile的state保证同步状态
-
Node节点实现同步和条件队列
-
ConditionObject配合LockSupport实现线程调度
-
Reentrant Lock
是什么:可重入的锁,提供了公平锁和非公平锁的实现,默认构造方法是非公平锁。
典型的模板方法模式实现,个人认为NonFairSync和FairSync也能算一种状态模式(传入boolean改变内部实现)
先看里面有什么:
-
实现Lock接口,重写lock和unlock方法
-
内部类Sync,继承了抽象类AbstractQueuedSynchronizr
-
内部类Sync的子类,FairSync和NonFairSync,分别是公平锁和非公平锁的实现
-
Condition实例化的支持,可调用await和signal实现线程调度
重点在于Sync:
前面说到AQS提供几个方法默认实现都是抛出不支持方法异常,也就是需要实现类去实现具体的尝试获取同步状态和尝试释放资源:tryAcquire和tryRelease的具体细节。
AQS自己对同步队列和等待条件的逻辑已经封装好了,在重要的acquire方法中,先判断:
若tryAcquire失败,会把当前线程创建成阻塞状态的Node并加入到阻塞队列的队尾,等待释放通知时再去尝试获取同步状态。
Sync的子类FairSync和NonFairSync重写了tryAcquire方法。
诸如阻塞队列相关的逻辑在AQS中实现了,我们重点关注Sync两个子类在lock上稍微的一点区别:
FairSync:公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
NonFairSync:非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
//父类Sync的nonfiarTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
可以看出:
-
公平锁的tryAcquire方法,会先去判断是否存在阻塞队列再去尝试CAS操作获取同步状态,若存在,说明已经有人在占有锁了,就会失败,执行addwaiter方法,生成一个node到阻塞队列乖乖排队。
-
非公平锁的tryAcurie方法,不会去判断阻塞队列,直接尝试CAS操作获取同步状态,若失败,乖乖执行addwaiter生成node去排队
-
若尝试获取锁成功,会把当前占用线程设置为当前线程。
可重入:
- 在tryAqcurie的时候,判断如果当前线程就是占有锁的线程,将state+1。
释放资源:
-
state设置成无锁状态,-1
-
exclusiveOwnerThread设置成null
-
调用unparkSuccessor方法,通知下一个队列中的线程。
ReentrantReadWriteLock
读写互斥的锁,基于ReentrantLock,适合读多写少场景
-
读写互斥
-
锁降级:写锁中再获取读锁,释放写锁获取读锁
-
不支持锁升级
-
state共同维护,高16位读锁,低16位写锁
-
获取写锁时:tryAcquire:读锁已经被读线程获取或其他写线程获取了写锁,则写锁获取失败
-
读锁时,线程owner是null,写锁获取到时才会设置当前线程为owner
/**
* 读写锁示例
*/
public class ReentrantRW {
public static void main(String[] args) throws InterruptedException {
// 执行A方法时,3个线程读,1个线程写,当写线程执行写操作时,其他的读操作会被阻塞
ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock rlock = rwlock.readLock();
ReentrantReadWriteLock.WriteLock wlock = rwlock.writeLock();
WriteThread w1 = new WriteThread(wlock);
w1.start();
for (int i = 0; i < 3; i++) {
ReadThread readThread = new ReadThread(rlock);
readThread.start();
}
}
public static void A() {
System.out.println(Thread.currentThread().getName()+ " : 正在执行。。。");
}
static class ReadThread extends Thread {
ReentrantReadWriteLock.ReadLock readLock;
public ReadThread(ReentrantReadWriteLock.ReadLock readLock) {
this.readLock = readLock;
}
@Override
public void run() {
readLock.lock();
System.out.println("读线程执行读操作");
A();
readLock.unlock();
}
}
static class WriteThread extends Thread {
ReentrantReadWriteLock.WriteLock writeLock;
public WriteThread(ReentrantReadWriteLock.WriteLock writeLock) {
this.writeLock = writeLock;
}
@Override
public void run() {
writeLock.lock();
System.out.println("写线程执行写操作。。。。");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
writeLock.unlock();
}
}