本文从源码角度解析读写锁,其实读写锁实现并不复杂,真正的难点还是AQS。如果对这方面不熟悉可以参考这篇文章AQS
ReentrantLock实现的是独占锁,是一种互斥操作,也就是说一次只能允许一个线程获取锁。而这种锁在某些程度上会降低线程的吞吐量,比如当我只读取数据时不会更改内存数据,所以加锁就是一种负担。所以独占锁也是一种保守的线程安全策略。而J.U.C中的读写锁实现了读锁/写锁的分离。(注意:锁分离并不是说用了两个锁。仍然是一个锁,不过这个锁允许多线程同时读,但是不允许“读写”或者“写写”
ReadWriteLock定义了获取读锁和写锁的方法,具体还是在ReentrantReadWriteLock中实现
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
读写锁实现分析
- 读写状态的分析
阅读过AQS我们都知道,AQS用state(整形变量)属性表示线程的同步状态。而读写锁上维护多个读线程或者一个写线程的状态。一个整形要怎么实现呢?
如果要用一个整形的state属性就要维护多种状态,就要”按位使用“,读写锁中将各变量分成了两部分。高16位表示读状态,低16位表示写状态。 - 读锁的获取与释放
我们首先来看ReentrantReadWriteLock中的tryAcquireShared方法
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
//判断是否为独占锁(写锁),如果是,判断是否为当前线程获取锁
return -1; //获取锁失败
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (!readerShouldBlock(current) && compareAndSetState(c, c + SHARED_UNIT)) {
//如果读线程不用等待(和是否公平有关系)并且设置状态成功
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
rh.count++;
return 1;//获取锁成功
}
return fullTryAcquireShared(current);//到这里说明读锁要等待或者设置状态未成功
}
final int fullTryAcquireShared(Thread current) {//不断尝试获取锁
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
for (;;) {
int c = getState();
int w = exclusiveCount(c);
if ((w != 0 && getExclusiveOwnerThread() != current) ||
((rh.count | w) == 0 && readerShouldBlock(current)))
return -1;
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
cachedHoldCounter = rh; // cache for release
rh.count++;
return 1;
}
}
}
我们来缕一缕上面的执行步骤
- 如果是已经有写线程持有锁,如果获取锁的线程不是当前线程,怎则返回,获取失败。(读写锁支持获取写锁的线程再获取读锁),否则,进行2
- 如果读线程请求锁数量达到了65535(包括重入锁),抛出error,否则进行3。
- 如果读线程不用等待(也就是非公平锁),并且增加读取锁状态数成功,那么就返回成功,否则进行4。
- 能到这一步说明CAS失败,然后不断循环知道CAS成功或者写锁被获取
接下来我么看看如何释放一个读锁
protected final boolean tryReleaseShared(int unused) {
HoldCounter rh = cachedHoldCounter;
Thread current = Thread.currentThread();
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
if (rh.tryDecrement() <= 0)
throw new IllegalMonitorStateException();
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
我们来分析一下这个释放代码,HoldCounter是什么东西,AQS里从没有见过。从名字里我们可以猜测,这应该是计数器,还应该是持有读锁的线程的计数器。而我们再看如何获得HoldCounter对象。
rh = readHolds.get()
具体源码就不贴了,readHolds的父类是ThreadLocal,见到ThreadLocal可以将一个对象和线程绑定在一起,那这个对象一定是HoldCounter的示例了。这就和我们上面猜的差不多了。HoldCounter绑定于一个线程,记录的应该是当前线程持有共享锁的数量。
接下来再看写锁的获取和释放,应为写锁是独占锁,这个和ReentrantLock比较相似,实现也比较简单
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if ((w == 0 && writerShouldBlock(current)) || !compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
- 如果有线程获取锁(c != 0),并且没有读锁(w==0即没有写锁)或者不是当前线程持有写锁,则获取锁失败,如果锁重入大于上限,抛出error
- 如果没有线程获取锁(c == 0),并且当前线程需要阻塞那么就返回失败;如果增加写线程数失败也返回失败。否则进行3。
- 设置独占线程(写线程)为当前线程,返回true。
protected final boolean tryRelease(int releases) {
int nextc = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
if (exclusiveCount(nextc) == 0) {
setExclusiveOwnerThread(null);
setState(nextc);
return true;
} else {
setState(nextc);
return false;
}
}
static final int SHARED_SHIFT = 16;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int exclusiveCount(int c) {
return c & EXCLUSIVE_MASK;
}
这个方法让状态量与某一个常量相与,得到的就是后16的值,也就是获取的写线程数