本章主要讲解lock的释放,如果未了解锁获取的读者可以查看上一篇。
//属于ReentrantLock 内部实现使用的sync即FairSync类的release
//但是FairSync并没有实现release最终实现的是他的父类Sync
public void unlock() {
sync.release(1);
}
//属于Sync arg参数为1
public final boolean release(int arg) {
//调用tryRelease 返回true 说明释放成功
if (tryRelease(arg)) {
//释放成功则需要唤醒下一个Node获取锁 获取当前head
Node h = head;
//如果h等于null 说明队列未创建,还并未初始化队列
//并且当前waitStatus 不等于 0 ,因为等于零说明还未进行shouldParkAfterFailedAcquire
//则没必要唤醒 因为在park前会多次检查队列,其中shouldParkAfterFailedAcquire
//会绝对的保证head状态为-1 具体原因请查看 上篇文章
if (h != null && h.waitStatus != 0)
//如果存在唤醒Node则唤醒
unparkSuccessor(h);
return true;
}
return false;
}
//属于ReentrantLock 尝试释放当前锁
protected final boolean tryRelease(int releases) {
//当前锁的状态 默认一定是 1 而在每次重入锁的时候都会进行加1
//代表着每次获取重入锁都需要有对应的释放锁 而此处则是释放锁的 -1操作
int c = getState() - releases;
//释放锁的线程不是锁持有的线程则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//释放状态默认是false失败
boolean free = false;
//如果c等于0 说明所有重入所获取的锁都进行了释放已经没有持有锁的状态
if (c == 0) {
//则释放成功并且设置锁持有对象为null
free = true;
setExclusiveOwnerThread(null);
}
//设置当前锁状态 因为释放锁的操作必须是持有锁的线程进行的
//所以此处仅仅是设置了state所以并不用cas操作因为其他线程都还卡着获取不了
//只有当前state释放了并且唤醒next才会有新的线程获取锁
//再次期间如果有新的线程进入则会加入到队列中而不是竞争锁
setState(c);
return free;
}
//属于AbstractQueuedSynchronizer
//唤醒next Node
private void unparkSuccessor(Node node) {
//获取传入node的等待状态 切记 此处Node为head 并且一定会是head
int ws = node.waitStatus;
//如果当前head的ws小于0则重置 他为1
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//获取node的next节点
Node s = node.next;
//如果当前节点为null 或者 ws状态大于0
if (s == null || s.waitStatus > 0) {
//不管是哪种都设置s为null 因为ws大于0 是取消状态需要跳过 null更不用说
//这两种状态都不需要唤醒所以此处跳过他设置为null
s = null;
//从尾部开始遍历 t不等于null并且不等于当前对象
for (Node t = tail; t != null && t != node; t = t.prev)
//直到获取到最后一个小于等于0的node设置为s
//因为为了保证顺序性 一定是获取的是按顺序执行的一个
//所以此处迭代不会停止会一直到结束 即t==node
if (t.waitStatus <= 0)
s = t;
}
//s == null说明没有找到需要获取锁的Node 要么队列为空 要么都取消获取锁
//相反s不等于null则唤醒s
if (s != null)
//这里需要注意的是 在获取锁的一篇中下方C代码第一段是每个Thread都有parker对象
//而此对象存储这每个Thead的锁和唤醒条件所以此处唤醒需要传入需要唤醒的线程
LockSupport.unpark(s.thread);
}
//属于LockSupport
//唤醒指定线程
public static void unpark(Thread thread) {
//线程不等于null则调用UNSAFE的unpark方法
if (thread != null)
UNSAFE.unpark(thread);
}
到此处又到了激动人心的jvm实现了。下面讲解unpark的C实现
//为了方便讲解流程将会跳过jvm的其他操作只管关键流程
//将传入的线程对象转换成 C++对象 unsafe.cpp 1300行
JavaThread thr = java_lang_Thread::thread(java_thread);
//调用thr的unpark方法 直接还有很多转换判断null之类的操作再次忽略
thr->parker()->unpark();
//其中的类在上篇已经介绍此处忽略忘记的小伙伴可以再去阅读一遍,回忆一下
//当前线程获取锁 锁的初始化在 线程创建时就初始化了而存储在Parker中
status = pthread_mutex_lock(_mutex);
//_cur_index在park时对_cond的索引做的记录所以此处可以直接唤醒
status = pthread_cond_signal (&_cond[_cur_index]);
//释放当前锁
status = pthread_mutex_unlock(_mutex);
Lock的公平锁到此基本结束,按照流程读者可以很快的了解他获取锁的流程,在简单的lock方法调用时整个系统发生的变化,从而学习设计者的设计思路。
网友评论