读锁不可以升级,写锁可以降级?
读锁是可并行的,写锁是串行的,那么如果多个读锁并行执行,遇到升级语句,就会出现死锁,比如t1要升级,那么就要等t2释放锁,而t2正好也在当t1释放锁。
加锁:
代码语言:javascript复制public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
代码语言:javascript复制protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 线程计数
int c = getState();
// 获取独占锁,就是写锁
int w = exclusiveCount(c);
// 线程计数!=0,表示重入;这里有两种:读锁,写锁
if (c != 0) {
// 如果是读锁,或者当前线程并非加锁线程,返回false,就会进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg))获取锁
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 判断写锁的可重入次数是否超过MAX_COUNT,超过抛异常
if (w exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 到这里,确定是写锁且当前线程已持有锁,重入锁 1
setState(c acquires);
return true;
}
// 走到这里,表示c=0,即没有写锁,也没有读锁
if (writerShouldBlock() ||
!compareAndSetState(c, c acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
代码语言:javascript复制final boolean writerShouldBlock() {
// 这个方法就是上一篇锁讲过的,查询是否有线程等待,有就排队
return hasQueuedPredecessors();
}
代码语言:javascript复制if (writerShouldBlock() ||
// 设置线程计数
!compareAndSetState(c, c acquires))
return false;
代码语言:javascript复制public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// 然后进入这个方法,上一篇讲过
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这里就是,队列里排队获取锁,和reentrantLock一样。
读锁:
代码语言:javascript复制public void lock() {
sync.acquireShared(1);
}
代码语言:javascript复制public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
代码语言:javascript复制protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 判断是否独占锁,是否当前线程;这里如果出现独占锁,那么该线程就返回-1,去排队
// 为什么呢?因为在读锁加锁的时候,也可能出现写锁进来,如果写锁进来了,写锁是排他锁,独占一把锁,那么读锁也要去排队
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 非独占锁(写锁),获取计数
int r = sharedCount(c);
// 没有线程等待,
if (!readerShouldBlock() &&
// 且读锁计数小于最大值,
r < MAX_COUNT &&
// 且加锁成功
compareAndSetState(c, c SHARED_UNIT)) {
// 读锁计数为0,没有读锁,第一个线程进来
if (r == 0) {
// 就给第一个线程对象赋值,
firstReader = current;
firstReaderHoldCount = 1;
// 如果说第一个线程对象等于当前线程对象,就是重入锁
} else if (firstReader == current) {
// 那么第一个线程内部计数 1
firstReaderHoldCount ;
// 如果以上条件都否定,即不是第一个线程,也不是重入锁
} else {
// 第一次进来,重入锁计数对象 = null
HoldCounter rh = cachedHoldCounter;
// 当前缓存中还没有,或者是第二次进来,rh不会空,那么判断rh的线程id是否和当前线程id相同,不同则表示其他线程进入
if (rh == null || rh.tid != getThreadId(current))
// 拿到缓存的重入锁对象:如果是同一个线程进入,就返回那个线程的缓存计数对象,如果是其他线程,就会初始化一个返回
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
// 缓存对象计数 1
rh.count ;
}
return 1;
}
return fullTryAcquireShared(current);
}
当前线程可重入锁的数量是内部是线程变量实现;
它自己本身保存了一份ThreadLocalMap对象,用于保存重入锁的线程
代码语言:javascript复制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();
}
假设第三个线程:
代码语言:javascript复制protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 判断是否独占锁,是否当前线程;这里如果出现独占锁,那么该线程就返回-1,去排队
// 为什么呢?因为在读锁加锁的时候,也可能出现写锁进来,如果写锁进来了,写锁是排他锁,独占一把锁,那么读锁也要去排队
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 非独占锁(写锁),获取计数
int r = sharedCount(c);
// 没有线程等待,
if (!readerShouldBlock() &&
// 且读锁计数小于最大值,
r < MAX_COUNT &&
// 且加锁成功
compareAndSetState(c, c SHARED_UNIT)) {
// 读锁计数为0,没有读锁,第一个线程进来
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount ;
} else {
// 第三次rh对象不为空
HoldCounter rh = cachedHoldCounter;
// rh != null,但tid不同 判断为true
if (rh == null || rh.tid != getThreadId(current))
// 从缓存对象中获取到缓存对象;存在返回,不存在初始化
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
// 缓存对象计数 1,这时缓存对象中第三个的计数是1
rh.count ;
}
return 1;
}
return fullTryAcquireShared(current);
}