ReentrantLock类的hasQueuedPredecessors方法和head节点的含义
部分启发来源自文章:Java并发编程--Lock
PART 1
1、如果h==t成立,h和t均为null或是同一个具体的节点,无后继节点,返回false。
2、如果h!=t成立,head.next是否为null,如果为null,返回true。什么情况下h!=t的同时h.next==null??,有其他线程第一次正在入队时,可能会出现。见AQS的enq方法,compareAndSetHead(node)完成,还没执行tail=head语句时,此时tail=null,head=newNode,head.next=null。
3、如果h!=t成立,head.next != null,则判断head.next是否是当前线程,如果是返回false,否则返回true(head节点是获取到锁的节点,但是任意时刻head节点可能占用着锁,也可能释放了锁(unlock()),未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
PART 2 解释为什么要判断:s.thread != Thread.currentThread()
评论区3楼的提问差点让我以为我这里理解错并写错了,现在是12月,文章是4月份写的,都快忘光了...仔细再把文章和源码读了读,发现本文写的确实不够详细,有个地方还写的有点问题,漏了一些细节,因此来补充一下。 ---20191217
1、
根据ReentrantLock的解锁流程,也就是下面四个方法,可以看到当线程释放锁之后还是会在队列的head节点,但会把head的后续可唤醒节点进行唤醒(unpark)
也就是说任意时刻,head节点可能占用着锁(除了第一次执行enq()入队列时,head仅仅是个new Node(),没有实际对应任何线程,但是却“隐式”对应第一个获得锁但并未入队列的线程,和后续的head在含义上保持一致),也可能释放了锁(unlock()),未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁
public void unlock() {
sync.release(1);
}
2、
尝试释放锁,释放成功后把head.next从阻塞中唤醒
从这里以及后续的3和4可以看出,虽然线程已经释放了锁(state设置为0),但是并没有把head指向链表的下个节点(即进行类似head = head.next的操作)
这里就对应的第1点里说的,如果这里能看到,那么久可以直接看第5点了
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
3、
把state-1
当state=0时,把exclusiveOwnerThread设置为null,说明线程释放了锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
4、
把head.next指向下一个waitStatus<=0的节点,并把该节点从阻塞中唤醒
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
// 这里没看懂为什么要从tail节点倒序遍历?
// 不是应该从head.next节点开始遍历更快嘛?
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
5、
需要提前知道一点:hasQueuedPredecessors()方法只在tryAcquire()方法里面被调用执行过,hasQueuedPredecessors()返回false表示要尝试获取锁
线程加锁的流程是:.lock() -> .acquire() -> tryAcquire()
这里我们先假设一个场景:A线程获取到了锁,然后B线程尝试去获取锁但是获取不到,此时链表的head是对用A线程,head.next对应B线程
当在B线程在第2行的tryAcquire()里面无法获取到锁时,线程B会通过下面第3行的addWaiter()方法被加入到等待链表当中,然后在第3行的acquireQueued()方法和第38行的parkAndCheckInterrupt()中park进入等待状态
在A线程释放锁之后,B线程会从38行处开始重新苏醒然后进入for(;;)循环,当B线程执行到第28行即再次执行tryAcquire()时,然后就会依次执行hasQueuedPredecessors()和s.thread != Thread.currentThread()。由前文可知,此时head仍然指向A线程,head.next也就是此处的s指向的是B线程,也同时是当前线程,所以s.thread != Thread.currentThread()为false,即此时需要尝试获取锁(再次重复这句话:未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)。
当此处B线程终于获得锁之后,会在第30行处把head指向B线程对应的链表结点。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
// 省略部分不重要的
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 省略部分不重要的
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 这里又执行了tryAcquire
if (p == head && tryAcquire(arg)) {
// 把head指向当前节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 获取不到锁,会在此处进入线程等待状态
// 后续被唤醒的话,也是从这里出来,然后继续for循环
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
ReentrantLock类的hasQueuedPredecessors方法和head节点的含义的更多相关文章
- 【高并发】又一个朋友面试栽在了Thread类的stop()方法和interrupt()方法上!
写在前面 新一轮的面试已经过去,可能是疫情的原因吧,很多童鞋纷纷留言说今年的面试题难度又提高了,尤其是对并发编程的知识.我细想了下,也许有那么点疫情的原因吧,但无论面试的套路怎么变,只要掌握了核心知识 ...
- StringBuffer类的delete()方法和deleteCharAt()方法的区别
引言 StringBuffer类的delete()方法和deleteCharAt()方法都是用来删除StringBuffer字符串中的字符 区别 1.对于delete(int start,int en ...
- 关于Object类的equals方法和hashCode方法
关于Object类的equals的特点,对于非空引用: 1.自反性:x.equals(x) return true : 2.对称性:x.equals(y)为true,那么y.equals(x)也为tr ...
- 并发基础篇(六):线程Thread类的start()方法和run()方法【转载】
[转载] 一.初识java的线程是通过java.lang.Thread类来实现的.VM启动时会有一个由主方法所定义的线程.可以通过创建Thread的实例来创建新的线程.每个线程都是通过某个特定Thre ...
- Android中Path类的lineTo方法和quadTo方法画线的区别
转载:http://blog.csdn.net/stevenhu_223/article/details/9229337 当我们需要在屏幕上形成画线时,Path类的应用是必不可少的,而Path类的li ...
- Object类的toString方法和equals方法
Object类 概述 java.long.Object 类是java语言中的根类,即所有类的父类.它中描述的所有方法子类都可以使用.在对象实例化的时候,最终的父类就是Object 类Object是类层 ...
- emberjs重写补充类之reopen方法和reopenClass方法
无需一次性将类定义完全,你可以使用reopen方法来重新打开(reopen)一个类并为其定义新的属性. Person.reopen({ isPerson: true }); Person.create ...
- Java基础知识强化84:System类之exit()方法和currentTimeMillis()方法
1. exit方法: public static void exit(int status): 终止当前正在运行的Java虚拟机.参数用作状态码:根据惯例,非0的状态码表示异常终止. 调用System ...
- 细说python类2——类动态添加方法和slots(转)
先说一下类添加属性方法和实例添加属性和方法的区别, 类添加属性属于加了一个以类为全局的属性(据说叫静态属性),那么以后类的每一个实例化,都具有这个属性.给类加一个方法也如此,以后类的每一个实例化都具备 ...
随机推荐
- python入门17 类和对象
类:一类事物的抽象化.概念: 类的变量(属于类的变量,定义在类的开始处) 成员变量(self.变量) 类的方法( @classmethod,cls参数) 成员方法( self参数 ) 静态方法 ...
- 2018.10.2 Eclipse中如何测地修改一个we项目步骤
找到项目的web.xml文件 大概的路径如下: 修改xml文件中的display-name 节点的值 下一步就是切换工作目录 显示的效果 打开最后一个文件修改 接下来找到这个文件 是部署的时候用的 运 ...
- ImageNet Classification with Deep Convolutional Nerual Networks(AlexNet)
Architecture: 整个网络8层,5个卷积层,3个全连接层 Relu Nonlinearity: 非饱和的relu比饱和的sigmoid或者tanh训练速度快很多,并有效解决梯度消失 Over ...
- sudo: Sorry, you must have a tty to run
The requiretty option in sudoers file The requiretty if set in sudo config file sudoers, sudo will o ...
- 【洛谷P2657】[SCOI2009] windy数
最近学习了一下数位DP 感觉记忆化搜索是比较好理解的 这篇博客对我有一定的启发https://www.cnblogs.com/zbtrs/p/6106783.html 总结了一下: 用数位DP的 ...
- Android学习笔记_JNI_c调用java代码
1.编写native方法(java2c)和非native方法(c2java): package com.example.provider; public class CallbackJava { // ...
- CSU 1023 修路(二分+模拟)
前段时间,某省发生干旱,B山区的居民缺乏生活用水,现在需要从A城市修一条通往B山区的路.假设有A城市通往B山区的路由m条连续的路段组成,现在将这m条路段承包给n个工程队(n ≤ m ≤ 300).为了 ...
- 第10章 使用STM32CubeMX新建工程
STM32Cube是一项意法半导体的原创活动, 通过减少开发工作.时间和成本, 使开发者的开发工作更轻松.STM32Cube 是一个全面的软件平台,包括了ST产品的每个系列.(如,STM32CubeF ...
- c# 调用服务返回结果模板化
一般我们返回一个结果,主要有返回值,执行结果信息,所以定义一个类 public class QuestResult { /// <summary> /// 返回值 ...
- python 之函数
一 函数的定义:对功能和动作的封装和定义.二 函数的格式:def 函数名(形参列表): 函数名就是变量名:规则就是变量的规则 函数体(return) ret = 函数名(实参列表)三 函数的返回值:函 ...