深入浅出多线程——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系统中的 ...
随机推荐
- 【ASP.NET MVC 学习笔记】- 01 理解MVC模式
本文参考:http://www.cnblogs.com/willick/p/3195560.html 1.MVC模式是软件系统的一种架构模式,它将软件分为三大模块: 模型(Model):封装业务逻辑以 ...
- 423. Reconstruct Original Digits from English (leetcode)
Given a non-empty string containing an out-of-order English representation of digits 0-9, output the ...
- Install a Redmine on Ubuntu system
# How to install a Redmine on Ubuntu system Ref to: https://www.linode.com/docs/applications/project ...
- bug:未考虑实际使用场景
最近bug比较多,汗颜. 1.需求背景 (1)app活动页面,用户参与并完成能够以1分钱价格购买指定商品(2)这个页面可分享至app以外的渠道,如微信.QQ等 2.这个bug的表现形式:用户在第三方渠 ...
- babel从入门到入门
babel从入门到入门 来源 http://www.cnblogs.com/gg1234/p/7168750.html 博客讲解内容如下: 1.babel是什么 2.javascript制作规范 3. ...
- 【20171104中】chrome自动刷新网页
target:刷访问量 tools:chrome / url start: s1:百度知道,https://zhidao.baidu.com/question/750134067096113532.h ...
- CUDA C Best Practices Guide 在线教程学习笔记 Part 1
0. APOD过程 ● 评估.分析代码运行时间的组成,对瓶颈进行并行化设计.了解需求和约束条件,确定应用程序的加速性能改善的上限. ● 并行化.根据原来的代码,采用一些手段进行并行化,例如使用现有库, ...
- Azkaban安装部署
在root的用户下搭建的 • Azkaban安装部署(可参照:http://azkaban.github.io/azkaban/docs/latest/) 1):前提 安装JDK,安装Hadoop,H ...
- AngularJS学习篇(十)
AngularJS Select(选择框) 使用 ng-options 创建选择框 在 AngularJS 中我们可以使用 ng-option 指令来创建一个下拉列表,列表项通过对象和数组循环输出,如 ...
- [DeeplearningAI笔记]ML strategy_2_4端到端学习
机器学习策略-端到端学习 End-to-end deeplearning 觉得有用的话,欢迎一起讨论相互学习~Follow Me 2.9 什么是端到端学习-What is End-to-end dee ...