回顾
前面我们已经看了写锁是怎样进行获取和释放的,下面就来看看读锁的获取和释放
读锁
写锁是一个排他锁,而读锁却是一个共享锁,而且还支持可重入,并且能被多个线程同时获取
读锁的获取
可以看到,其底层的实现也是AQS,只不过是AQS的共享模式,并且执行的是AQS的acquireShared方法
源码如下
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
可以看到获取共享锁的主要方法为tryAcquireShared
tryAcquireShared
源码如下
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
从代码上可以看到,共享锁其实还挺复杂
前面的步骤都还好理解,重点在于读锁怎么去存储各个线程的重入次数的
记录各个线程的重入次数
对于被firstReader记录的线程还好说,通过firstReaderHoldCount进行记录即可
但对于其他线程,则是利用ThreadLocalMap来进行存储的
HoldCounter
每个线程对应的缓存其实就是一个HoldCounter
可以看到,这个类里面有两个属性
从前面可以看到,当前缓存为空,或者该缓存并不属于当前线程(线程ID不对应),那么就会走readHolds的get方法
从判断条件可以看到,这个get方法应该会做两种事情
- 缓存为空,证明没创建,需要进行初始化
- 缓存不属于当前线程,需要进行切换线程
从源码中可以看到,readHolds本质上是一个ThreadLocalHoldCounter对象,并且继承了ThreadLocal,并且泛型是HoldCounter,所以ThreadLocalHoldCounter拥有ThreadLocal的功能,而ThreadLocal有存储功能,可以将元素存储在ThreadLocalMap中
下面就来看看get方法是做什么的
readHolds.get
可以看到,这个方法来自ThreadLocal源码如下
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
从这里大概就可以看到大致的模型了
当前线程的ThreadLocalMap中存储了以ThreadLocalHoldCounter的父类ThreadLocal为键的,HoldCounter为值的键值对,键是每个线程都能拿到的,但这里能进行区分是因为每个线程都存在了自己的ThreadLocalMap中,这样就进行区分了
可以看到,其底层其实是一个Entry数组
setInitialValue方法
这个方法其实就是当ThreadLocalMap没有创建的时候,需要进行初始化的时候调用
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
set方法
当count属性为0时,会调用set方法,这个方法其实是用来修改值的或者为空时进行初始化的,没太看懂这个是干嘛的。。是为0时还没有进入缓存吗?
最后初始化缓存和容器了之后,就进行对缓存的count属性进行自增1了,这样就对应记录了每个线程的重入次数了
假如第一次获取失败
第一次获取失败是会执行fullTryAcquireShared方法的,这个方法名也挺有讲究,翻译为完全尝试,前面先尝试,尝试失败就进行完全尝试
源码如下
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
}
else if (readerShouldBlock()) {
if (firstReader == current) {
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh;
}
return 1;
}
}
}
可以看到,这个方法其实是在CAS获取读锁的基础上,添加了阻塞功能,也就是说完整的尝试获取锁,不仅是判断写锁的状态和读锁的状态,还需要去考虑是否要阻塞
而考虑这个阻塞问题,其实就是关于公平锁和非公平锁的判断了,这里要注意一点的就是可重入锁是没有排队这个概念的!!!!
|