部分启发来源自文章: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节点的含义的更多相关文章

  1. 【高并发】又一个朋友面试栽在了Thread类的stop()方法和interrupt()方法上!

    写在前面 新一轮的面试已经过去,可能是疫情的原因吧,很多童鞋纷纷留言说今年的面试题难度又提高了,尤其是对并发编程的知识.我细想了下,也许有那么点疫情的原因吧,但无论面试的套路怎么变,只要掌握了核心知识 ...

  2. StringBuffer类的delete()方法和deleteCharAt()方法的区别

    引言 StringBuffer类的delete()方法和deleteCharAt()方法都是用来删除StringBuffer字符串中的字符 区别 1.对于delete(int start,int en ...

  3. 关于Object类的equals方法和hashCode方法

    关于Object类的equals的特点,对于非空引用: 1.自反性:x.equals(x) return true : 2.对称性:x.equals(y)为true,那么y.equals(x)也为tr ...

  4. 并发基础篇(六):线程Thread类的start()方法和run()方法【转载】

    [转载] 一.初识java的线程是通过java.lang.Thread类来实现的.VM启动时会有一个由主方法所定义的线程.可以通过创建Thread的实例来创建新的线程.每个线程都是通过某个特定Thre ...

  5. Android中Path类的lineTo方法和quadTo方法画线的区别

    转载:http://blog.csdn.net/stevenhu_223/article/details/9229337 当我们需要在屏幕上形成画线时,Path类的应用是必不可少的,而Path类的li ...

  6. Object类的toString方法和equals方法

    Object类 概述 java.long.Object 类是java语言中的根类,即所有类的父类.它中描述的所有方法子类都可以使用.在对象实例化的时候,最终的父类就是Object 类Object是类层 ...

  7. emberjs重写补充类之reopen方法和reopenClass方法

    无需一次性将类定义完全,你可以使用reopen方法来重新打开(reopen)一个类并为其定义新的属性. Person.reopen({ isPerson: true }); Person.create ...

  8. Java基础知识强化84:System类之exit()方法和currentTimeMillis()方法

    1. exit方法: public static void exit(int status): 终止当前正在运行的Java虚拟机.参数用作状态码:根据惯例,非0的状态码表示异常终止. 调用System ...

  9. 细说python类2——类动态添加方法和slots(转)

    先说一下类添加属性方法和实例添加属性和方法的区别, 类添加属性属于加了一个以类为全局的属性(据说叫静态属性),那么以后类的每一个实例化,都具有这个属性.给类加一个方法也如此,以后类的每一个实例化都具备 ...

随机推荐

  1. fastcgi+lighttpd+c语言 实现搜索输入提示

    1.lighttpd 服务器 lighttpd是一个比较轻量的服务器,在运行fastcgi上效率较高.lighttpd只负责投递请求到fastcgi. centos输入yum install ligh ...

  2. HTTP请求方式中8种请求方法(简单介绍)

    简单介绍 HTTP是超文本传输协议,其定义了客户端与服务器端之间文本传输的规范.HTTP默认使用80端口,这个端口指的是服务端的端口,而客户端使用的端口是动态分配的.当我们没有指定端口访问时,浏览器会 ...

  3. Angularjs实例1

    <!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...

  4. 掘金上发现的有趣web api

    本篇文章主要选取了几个有趣且有用的webapi进行介绍,分别介绍其用法.用处以及浏览器支持度 page lifecycle onlineState(网络状态) device orientation(陀 ...

  5. python的格式化输出(format,%)

    皇城PK Python中格式化字符串目前有两种阵营:%和format,我们应该选择哪种呢? 自从Python2.6引入了format这个格式化字符串的方法之后,我认为%还是format这根本就不算个问 ...

  6. 【SQL】Oracle的交集、并集、差集

    假设有表tableA.tableB,他们都有字段id和name 交集:INTERSECT (适用于两个结果集) select a.id, a.name from tableA a INTERSECT ...

  7. win7 bat copy 一个文件 到另外的文件夹内,路径得用引号哦

    win 7 的 用引号 把路径引起来 ,但是win10 的可以不用哦 !

  8. iOS 直播类APP开发流程解析

    1 . 音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放显示1.数据采集:摄像机及拾音器收集视频及音频数据,此时得到的为原始数据涉及技术或协议:摄像机:CCD.C ...

  9. js的事件流你真的弄明白了吗?

    当浏览器发展到第四代时候,浏览器开发团队遇到了一个有意思的问题:页面的哪一部分会拥有某个特地的事件?要明白这个问题问的是什么,可以想象画在纸上的一组同心圆,如果你把手指放在圆心上,那么你的手指指向的不 ...

  10. C / C ++ 基于梯度下降法的线性回归法(适用于机器学习)

    写在前面的话: 在第一学期做项目的时候用到过相应的知识,觉得挺有趣的,就记录整理了下来,基于C/C++语言 原贴地址:https://helloacm.com/cc-linear-regression ...