先看一下ReentrantReadWriteLock的继承图
ReadWriteLock接口很简单,就两个方法,就是返回读锁、写锁的方法
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
可以发现它没有继承Lock接口,再想想平常使用是lock.writeLock().lock()这样子的,所以接着往下看ReentrantReadWriteLock怎么实现writeLock方法的
public ReentrantReadWriteLock.WriteLock writeLock() {
return writerLock;
}
//writerLock是ReentrantReadWriteLock类的一个成员变量
private final ReentrantReadWriteLock.WriteLock writerLock;
//它在构造方法里被初始化
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
//而WriteLock的构造方法是
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
WriteLock是ReentrantReadWriteLock的一个内部类,和ReentrantLock很相似,锁的相关方法交给了ReentrantReadWriteLock的内部类Sync来代理
可以看到WriteLock实现了Lock接口,所以有lock()、unlock()等方法,下面就开始分析其实现逻辑,由于有公平锁、非公平锁两种,那就先分析非公平锁,从lock()方法入手
PS: 由于和ReentrantLock差不多,一些地方就不多解析,相关文章:ReentrantLock笔记(二) – 公平/非公平锁源码分析
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();//设置中断标志
}
看到tryAcquire()方法又是要看具体Sync类怎么实现
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);//获取已经分配了的排它资源数
if (c != 0) {//不为0说明有线程请求获得了资源(共享资源或排它资源)
if (w == 0 || current != getExclusiveOwnerThread())
return false;//说明有其他线程获得了共享资源(读锁),排它资源(写锁)就获取失败
if (w + exclusiveCount(acquires) > MAX_COUNT)//资源数获取过多
throw new Error("Maximum lock count exceeded");
setState(c + acquires);//设置资源状态数
return true;
}
//到这里说明还未有线程获得资源,所以
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);//这里获得写锁,并设置了拥有者为当前线程
return true;
}
看看writerShouldBlock是啥?
abstract boolean writerShouldBlock();
是个抽象方法,看非公平锁(NonfairSync)如何实现
final boolean writerShouldBlock() {
return false;
}
直接返回了false,那就继续看CAS设置状态compareAndSetState(c, c + acquires)是否成功了,不成功就说明被其他线程抢先获得了资源,所以就返回false。
回顾一下上面 if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) , tryAcquire获取失败那就要看acquireQueued方法了。
该方法在前面的文章ReentrantLock笔记(二) – 公平/非公平锁源码分析有分析过,所以大致说一下:该方法是把当前线程加入sync队列,并开始等待自己排到队列的第二个节点,就会开始继续尝试tryAcquire,成功就会获得资源,成为队列头节点。
总结一下流程:
- 步骤A:线程请求写锁,先判断当前分配资源数是否大于0
- 步骤B:大于0说明有线程获得了资源,就进行排它资源数判断,如果等于0,那就说明分配的资源是共享资源(读锁),那就不能获得写锁了;如果不等于0,那就要判断之前是不是当前线程获得了排它资源,不是的话就失败
- 步骤C:当步骤A中判断分配的资源数不是大于0,那就是等于0,说明现在没有线程获得资源(锁),直接进行CAS操作,成功就是获得资源(锁),失败就返回false表示失败
- 步骤D:步骤B、步骤C都失败的话,就要进入sync队列,并开始休眠,等待自己排到队列的第二个节点,就会开始继续尝试tryAcquire,成功就会获得资源,成为队列头节点,失败就继续休眠。
现在来看公平锁(FairSync)如何实现,主要看writerShouldBlock()这个抽象方法它怎么实现
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
hasQueuedPredecessors()是在当前线程排在了sync队列中的第二个节点时会返回false, 回顾下前面if(writerShouldBlock() ||!compareAndSetState(c, c + acquires)), writerShouldBlock()返回false才会进行后面的CAS操作,这样就保证了下一次获得资源(锁)的线程是按队列出队的顺序获得的,所以是公平的
来看看unlock方法,公平锁和非公平锁都是一样的,比较简单
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//唤醒后置节点
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}