深入浅出多线程——ReentrantLock (一)文章中介绍了该类的基本使用,以及在源码的角度分析lock()、unlock()方法。这次打算在此基础上介绍另一个极为重要的方法newCondition(),其实这类已经不属于ReentrantLock的范畴了,是java.util.concurrent.locks.Condition接口的一个实现,位于AbstractQueuedSynchronizer(简称:AQS)中的内部类ConditionObject。

  该类提供了await*()、signal*()等方法。本次只对await()、signal()方法在源码的角度进行解析。

原理分析

await()方法分析

ConditionObject.await()
         public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

  1、判断线程是否被中断,如果被中断则抛出 InterruptedException。

  2、调用了addConditionWaiter()方法,将当前线程添加到等待队列中。

  3、第5行,调用fullyRelease(Node)方法,尝试释放当前线程并返回释放前的state值。

  4、第7行,while循环条件为isOnSyncQueue(Node) 取反,也就是说该方法必须返回false才能进入循环体。进入后调用LockSupport.park()挂起当前线程。

  5、等待调用signal()方法,将其加入同步队列等待调度到。调度到后,线程接着往下走,因为此时已经在同步队列中,while循环跳出。

  6、来到第12行,尝试将state的值还原到await之前,如果还原成功,则线程继续往下走。如果不成功说明再此期间,已经被其他线程占用,则继续等待。

  7、如果当前等待的节点有下游等待节点,在进行清理被取消的等待节点。

  8、方法执行完毕后,则继续执行线程的业务,直至调用到unlock()。

ConditionObject.addConditionWaiter()
         private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}

  1、判断lastWaiter是否为有效状态,如果无效,执行unlinkCancelledWaiters()方法,将其无效的节点清理掉。将当前线程设置为一个node,waitStatus值为-2。

  2、判断lastWaiter是否为null,如果为null代表队列为空,那么将创建的node赋值到队列的firstWaiter属性上,如果不为null,则链接到队列最后一个node的下游(因为第一次调用await()方法,此时lastWaiter肯定为空)。然后将队列的lastWaiter属性设置为新建的node。

Condition.fullyRelease(Node)
    final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}

  1、获取当前线程的state值、然后调用AQS.release(int)尝试释放当前线程,如果释放成功则返回线程state。
有关AQS.relase(int)方法的分析,已经在前一篇文章中进行详细说明。如需查看请点击

  2、如果没有释放成功,则抛出异常 IllegalMonitorStateException,并且将node.waitStatus状态设置为取消。

AbstractQueuedSynchronizer.isOnSyncQueue(Node)
     final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true; return findNodeFromTail(node);
}

  这个方法从字面意思为当前节点是否在同步列队中,如果在则返回true。这个地方个人表示挺难理解的,在这里我尽量用通俗易懂的方式进行阐述。

  1、第2行,判断当前node的waitStatus值是否为-2(await())或者node.prev是否为null,两者满足其一就返回false。判断node的waitStatus的值是否为-2很好理解,调用了await后,第一次来到这个方法,肯定是成立的。判断node.prev是否为null,这个地方是比价绕的,第一次进来同样为null。在什么时候这个条件不成立呢?当时看的时候就有点头晕,于是就开启联想模式,终于有了点思路,就是说调用await()方法的线程一定处于同步列队的head,此时他的prev一定是null,在看过signal()方法后,看到线程被其唤醒时需要重新加入同步队列。这时只能放到队列的末尾,node.prev就被指向了他的上游节点。

  2,当第一个判断全部不成立时,接着执行了第二个判断,node.next是否为null,不为null则返回true。这个地方是他已经处于了同步队列,并且已经有了下游节点。

  3,前两个判断都不满足的情况下直接调用了findNodeFromTail(node),字面意思是从队列的末尾查找node,什么情况下会调用到这个方法呢?node本身就处于末尾时调用。

signal()方法分析

ConditionObject.signal()
         public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}

  获取到第一个等待者,如果不为null则执行doSignal(Node)

ConditionObject.doSignal(Node)
         private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}

  1、进入do-while循环体,判断first.nextWaiter是否为null,如果为null则将lastWaiter置为null。

   2、紧接着进入while条件,继续循环的条件为调用transferForSignal(Node)返回false,并且firstWaiter不为null。

AbstractQueuedSynchronizer.transferForSignal(Node)
     final boolean transferForSignal(Node node) {

         if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false; Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}

  1、首先将当前node的waitStatus的值由-2设置为0,并判断是否返回false。如果返回false,则说明该线程被取消。

  2、调用熟悉的enq(Node)方法,把当前node拼接到同步列队中并返回node上游节点p。

  3、此时p的waitStatus等于0。所以直接进入第二个判断条件,将p的waitStatus从0设置为-1。如果此时设置失败后,将直接当前node解锁。设置失败的前提个人理解为:p处于运行中,也就是说调用了LockSupport.unpark(p.thread),还有一种情况就是线程被取消。

 总结

   1、Condition提供了一套线程等待及唤醒机制,与之匹配为Object.wait/notify等方法。但后者的使用条件为synchronized,不能直接在ReentrantLock中应用。

   2、Condition可以在一个lock对象中存在多个,灵活方便。

   3、ConditionObject类中也存在了大量的AQS操作,同样说明AQS是同步框架的基础框架。

深入浅出多线程——ReentrantLock (二)的更多相关文章

  1. 深入浅出多线程——ReentrantLock (一)

    ReentrantLock是一个排它重入锁,与synchronized关键字语意类似,但比其功能更为强大.该类位于java.util.concurrent.locks包下,是Lock接口的实现类.基本 ...

  2. 简述Java多线程(二)

    Java多线程(二) 线程优先级 Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行. 优先级高的不一定先执行,大多数情况是这样的. 优 ...

  3. [.net 面向对象程序设计进阶] (17) 多线程(Multithreading)(二) 利用多线程提高程序性能(中)

    [.net 面向对象程序设计进阶] (17) 多线程(Multithreading)(二) 利用多线程提高程序性能(中) 本节要点: 上节介绍了多线程的基本使用方法和基本应用示例,本节深入介绍.NET ...

  4. Java多线程(二)关于多线程的CPU密集型和IO密集型这件事

    点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...

  5. Java多线程(二) —— 线程安全、线程同步、线程间通信(含面试题集)

    一.线程安全 多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的. 讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会 ...

  6. java多线程系列(二)

    对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...

  7. java多线程系列(二)---对象变量并发访问

    对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...

  8. java多线程解读二(内存篇)

    线程的内存结构图 一.主内存与工作内存 1.Java内存模型的主要目标是定义程序中各个变量的访问规则.此处的变量与Java编程时所说的变量不一样,指包括了实例字段.静态字段和构成数组对象的元素,但是不 ...

  9. 多线程编程之Linux环境下的多线程(二)

    上一篇文章中主要讲解了Linux环境下多线程的基本概念和特性,本文将说明Linux环境下多线程的同步方式. 在<UNIX环境高级编程>第二版的“第11章 线程”中,提到了类UNIX系统中的 ...

随机推荐

  1. 使用vsftpd+nginx搭建一个文件服务器

    一:安装vsftpd 1.安装 [root@localhost jack]# yum -y install vsftpd 安装完后,有/etc/vsftpd/vsftpd.conf 文件,是vsftp ...

  2. 动态主机配置协议DHCP

    一.什么是DHCP DHCP,动态主机配置协议,提供一种称为“即插即用连网”的机制,允许一台计算机加入新的网络和获取IP地址而不用手工配置. 二.DHCP工作原理和工作流程 DHCP服务器被动打开UD ...

  3. 【深度学习系列】手写数字识别卷积神经--卷积神经网络CNN原理详解(一)

    上篇文章我们给出了用paddlepaddle来做手写数字识别的示例,并对网络结构进行到了调整,提高了识别的精度.有的同学表示不是很理解原理,为什么传统的机器学习算法,简单的神经网络(如多层感知机)都可 ...

  4. jquery的遍历选择器-随机整理下

    我从w3c上截了一张图,如图所示: 下面我们来详细说一说.这些选择器. 1.add() 方法将元素添加到匹配元素的集合中.例子: .add(selector) $("div").a ...

  5. 安装PyQt5之后mayavi和VTK不能使用

    mayavi在显示数据的过程中需要调用PyQt4的GUI方法产生应用框架.但是新发布的PyQt5和PyQt4在很多方面都是不兼容的,这也就导致了用mayavi编写的程序运行失败.在实践之后,我的解决方 ...

  6. Lua中metatable和__index的联系

    Lua中metatable和__index的联系 可以参考 http://blog.csdn.net/xenyinzen/article/details/3536708 来源 http://blog. ...

  7. C++实现引用和被引用对象分离

    上一篇博客简单介绍了C++中引用的底层实现,接下来,通过简单的代码验证如何将引用和被引用对象分离. 代码如下; #include <iostream> #include<string ...

  8. JAVANIO通道

    package com.nio.test; import java.io.FileInputStream; import java.io.FileNotFoundException; import j ...

  9. Spring Security Ajax 被拦截

    背景是项目中使用Spring Security 进行安全控制 再使用Ajax的时候会报 403(ajax get  方式是没问题的 post 的时候会报) Spring Security 原本是 防止 ...

  10. ueditor 和 umeditor 粘贴过滤问题

    最近遇到需要将WORD WPS等复制的带有格式的内容粘贴到富文本编辑器里面去掉冗余的HTML,只保留最有用的部分. 第一步肯定是先查官方文档了. http://fex.baidu.com/uedito ...