公平的获取同步状态
非公平锁尝试获取同步状态 流程类似就不过多描述
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;
}
那公平锁如何来实现获取同步状态呢?
其实看过上篇AQS文章的同学就知道了,在上篇文章中已经说过
只需要在尝试获取同步状态前加上一个条件:队列中是否有前置任务(即在队列中FIFO排队获取)
公平锁也是这么去实现的,前置条件hasQueuedPredecessors
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;
}
ReentrantReadWriteLock
功能与实现
ReentrantReadWriteLock在ReentrantLock功能的基础上,提供读写锁的功能,让锁的粒度更细
在一些读多写少的场景下是允许同时读的,允许多个线程获取,其实想到了AQS的共享式,读锁也就是共享式
在读读的场景下,都是读锁/共享锁,不会进行阻塞
在读写、写读、写写的场景下,都会进行阻塞
比如要获取写锁时,需要等待读锁、写锁都解锁;要获取读锁时,需要等待写锁解锁
ReentrantReadWriteLock 在 ReentrantLock 的基础上增加ReadLock
和WriteLock
分别作为读锁和写锁
实际上读锁就是共享锁、写锁就是独占锁,在实现加锁、解锁的方法时分别调用共享式、独占式的获取、释放同步状态即可
在构造时,读写锁中实际使用的都是同一个AQS
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
//读锁构造
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
//写锁构造
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
即同步状态会被读写锁共享,那么它们如何查看/修改自己的那部分同步状态呢?
在读写锁中,同步状态被一分为二,高16位的同步状态是读锁的,低16位的同步状态是写锁的
当线程获取写锁时,写状态+1,由于写状态在低位,相当于同步状态+1
当线程获取读锁时,读状态+1,由于读状态在高位,相当于同步状态+(1<<16)
写锁获取
写锁的获取实现在sync.tryAcquire
中 sync可以是公平也可以是非公平,实际上是独占式的获取
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//得到同步状态c
int c = getState();
//得到写状态(同步状态低16位 与上 全1)
int w = exclusiveCount(c);
if (c != 0) {
//同步状态不为0,写状态为0,说明读状态不为0,读锁已经被获取,此时获取写锁失败
//同步状态不为0,写状态也不为0,查看当前线程是否是获取写锁的线程,不是的话获取写锁失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//只有当前线程获取过写锁才能进入这里
//如果原来的写状态+这次重入的写状态 超过了 同步状态的0~15位 则抛出异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//设置同步状态 因为写状态在低16位所以不用左移 (重入累加)
setState(c + acquires);
return true;
}
//同步状态为0 无锁时
//writerShouldBlock在非公平锁下返回false 在公平锁下查看是否有前驱任务
//如果CAS失败则返回false
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//CAS成功则 设置当前线程为获得独占锁(写锁)的线程
setExclusiveOwnerThread(current);
return true;
}
查看源码可以知道:
- 当有锁时(同步状态不为0情况),如果只有读锁(没有写锁),那么直接失败;如果只有写锁则查看当前线程是否为获取写锁的线程(重入情况)
- 当无锁时进行CAS获取写锁,成功则设置获取写锁的线程,失败则返回
根据源码分析可以知道,写锁允许重入,并且获取写锁时,如果有读锁会被阻塞
写锁释放
写锁的释放实现在sync.tryRelease
中
protected final boolean tryRelease(int releases) {
//判断当前线程是不是获取写(独占)锁的线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//新状态
int nextc = getState() - releases;
//如果新状态低16位为0