接着上一篇的,在加了锁之后,就到了释放锁的环节
话不多说,直接上源码
从释放锁进入
readLock.unlock();
public void unlock() {
//还是默认的非公平锁的释放
sync.releaseShared(1);
}
释放锁
public final boolean releaseShared(int arg) {
//1、尝试释放锁 如果释放之后, 锁的数量为0 则会执行第2步
if (tryReleaseShared(arg)) {
//2、唤醒其他线程
doReleaseShared();
return true;
}
return false;
}
1、尝试释放锁
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//当前线程是第一个获取读锁的线程
if (firstReader == current) {
//持有的锁的数量为1 直接将firstReader置空
if (firstReaderHoldCount == 1)
firstReader = null;
else
//将持有的锁的数量-1
firstReaderHoldCount--;
} else {
//获取缓存的线程持有的锁的计数器
HoldCounter rh = cachedHoldCounter;
//不是本线程的计数器
if (rh == null || rh.tid != getThreadId(current))
//从threadlocal中获取
rh = readHolds.get();
int count = rh.count;
//持有锁的数量<=1 直接从threadlocal中移除
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
//将持有的锁
--rh.count;
}
for (;;) {
//获取锁数量
int c = getState();
//为什么这么操作呢? 这就和上锁关联起来了
//每加一个锁。state的值就增加SHARED_UNIT 所以每释放一次,也需要减SHARED_UNIT
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
//锁释放成功之后,判断锁的数量是否为0 并且返回
return nextc == 0;
}
}
2、唤醒其他线程 在分析加锁的时候,2.2 这一块如果下一个结点需要共享锁,则会唤醒这个线程。 同样,在释放掉一个锁之后,也会尝试唤醒下一个结点线程 源码如下
private void doReleaseShared() {
for (;;) {
Node h = head;
//判断队列不为空
if (h != null && h != tail) {
//获取头节点的状态
int ws = h.waitStatus;
//等待被唤醒的状态
if (ws == Node.SIGNAL) {
//将其状态改为0标识本结点不在作用于同步队列 如果失败,进行下一次循环
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//如果成功,唤醒下一个结点 这一步在上一篇加锁已经分析
unparkSuccessor(h);
}
//0状态的话 尝试将其修改为-3 失败的话进入下一次循环
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//这一块为什么这么做??没搞懂哦
if (h == head) // loop if head changed
break;
}
}
由上可知,在唤醒线程之后,线程会接着上一步继续往下执行,
也就是线程会接着parkAndCheckInterrupt()这个之后去往下走,
因为parkAndCheckInterrupt()让线程暂时堵塞,唤醒之后,就会接着往下走,
然后在一次去获取他的前置结点,并且尝试获取锁,也就是doAcquireShared()里面的自旋
到此位置,释放锁就已经结束了 下一篇分析公平模式下的写锁的加锁与释放
友情参考 https://www.cnblogs.com/waterystone/p/4920797.html