深入浅出多线程——ReentrantLock (二)
深入浅出多线程——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 (二)的更多相关文章
- 深入浅出多线程——ReentrantLock (一)
ReentrantLock是一个排它重入锁,与synchronized关键字语意类似,但比其功能更为强大.该类位于java.util.concurrent.locks包下,是Lock接口的实现类.基本 ...
- 简述Java多线程(二)
Java多线程(二) 线程优先级 Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行. 优先级高的不一定先执行,大多数情况是这样的. 优 ...
- [.net 面向对象程序设计进阶] (17) 多线程(Multithreading)(二) 利用多线程提高程序性能(中)
[.net 面向对象程序设计进阶] (17) 多线程(Multithreading)(二) 利用多线程提高程序性能(中) 本节要点: 上节介绍了多线程的基本使用方法和基本应用示例,本节深入介绍.NET ...
- Java多线程(二)关于多线程的CPU密集型和IO密集型这件事
点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...
- Java多线程(二) —— 线程安全、线程同步、线程间通信(含面试题集)
一.线程安全 多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的. 讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会 ...
- java多线程系列(二)
对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...
- java多线程系列(二)---对象变量并发访问
对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...
- java多线程解读二(内存篇)
线程的内存结构图 一.主内存与工作内存 1.Java内存模型的主要目标是定义程序中各个变量的访问规则.此处的变量与Java编程时所说的变量不一样,指包括了实例字段.静态字段和构成数组对象的元素,但是不 ...
- 多线程编程之Linux环境下的多线程(二)
上一篇文章中主要讲解了Linux环境下多线程的基本概念和特性,本文将说明Linux环境下多线程的同步方式. 在<UNIX环境高级编程>第二版的“第11章 线程”中,提到了类UNIX系统中的 ...
随机推荐
- HTML5-前端开发很火且工资很高?
前言 晚上逛论坛看到一篇对从事HTML5前端开发的文章写的非常不错,和目前的市场形势差不多,然后我在其基础上给大家进行加工总结一下分享给大家.今天我们谈论的话题是<<为什么从事HTML5前 ...
- 如何用CropBox实现头像裁剪并与java后台交互
如何用CropBox实现头像裁剪并与java后台交互 参考网站:https://developer.mozilla.org/zh-CN/docs/Web/API/Blob 参考: http://blo ...
- 2017年11月Dyn365/CRM用户社区活动报名
UG是全球最大Dynamics的用户组织,由最终用户自发组织,由行业有经验的专家自愿贡献知识和经验的非营利机构,与会人员本着务实中立的态度,不进行推介产品,服务以及其他营销行为.在美国,微软Dynam ...
- LeetCode 136. Single Number (落单的数)
Given an array of integers, every element appears twice except for one. Find that single one. Note:Y ...
- Single linked list by cursor
有了指针实现看似已经足够了,那为什么还要有另外的实现方式呢?原因是诸如BASIC和FORTRAN等许多语言都不支持指针,如果需要链表而又不能使用指针,那么就必须使用另外的实现方法.还有一个原因,是在A ...
- 【Jquery系列】详解Jquery对象和Dom对象
问题描述 本篇文章主要讲解Jquery对象和DOM对象,主要围绕如下五个方面来介绍: Jquery对象和dom对象定义 Jquery对象与dom对象区别 Jquery对象及运用举例 dom对象及运用举 ...
- SSM框架中的注解,配置和控制器相关笔记
常规SSM实例 探索SSM理论的前提,应该是在对框架基础的运作方式有一定了解,以下是个人Android后台项目,用SSM框架快速搭建,以下是代码,主要 观察结构. 代码结构: model实体类 Ida ...
- gulp learning note
为啥写这一片文章呢? 主要是为了温故而知新和分享,也是为了更加促进自己的学习! 前端自动化工具很多 有grunt gulp webpack 等 这次主要分享下gulp的学习经验,让自己更好的总结 ...
- C# group 子句
group 子句返回一个 IGrouping<TKey,TElement> 对象序列,这些对象包含零个或更多与该组的键值匹配的项. 例如,可以按照每个字符串中的第一个字母对字符串序列进行分 ...
- HTML学习笔记 基础表格案例 第二节 (原创) 参考使用表
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...