深入浅出多线程——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系统中的 ...
随机推荐
- 将摄像头的读入的人像放入背景视频中_with_OpenCV_in_Python
import cv2 import numpy as np import time cap = cv2.VideoCapture(0) background_capture = cv2.VideoCa ...
- C陷阱和缺陷学习笔记
这段时间把<C陷阱和缺陷>看了,没时间自己写总结.就转一下别人的学习笔记吧http://bbs.chinaunix.net/thread-749888-1-1.html Chapter 1 ...
- SimpleDateFormat时间格式化存在线程安全问题
想必大家对SimpleDateFormat并不陌生.SimpleDateFormat 是 Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调 ...
- 双向链表--Java实现
/*双向链表特点: *1.每个节点含有两个引用,previos和next,支持向前或向后的遍历(除头节点) *2.缺点插入或删除的时候涉及到引用修改的比较多 *注意:下面的双向链表其实也实现了双端链表 ...
- Python数据库查询之组合条件查询-F&Q查询
F查询(取字段的值) 关于查询我们知道有filter( ) ,values( ) , get( ) ,exclude( ) ,如果是聚合分组,还会用到aggregate和annotate,甚至还有万能 ...
- 编译错误:expected an indented block
python 对缩进要求非常严格,当运行时出现以下几种情况时,就需要修改缩进: 1.expected an indented block 直接缩进4个space空格或者一个tab.
- Vue源码学习02 初始化模块init.js
接上篇,我们看到了VUE分了很多模块(initMixin()stateMixin()eventsMixin()lifecycleMixin()renderMixin()),通过使用Mixin模式,都是 ...
- express紧急回顾随笔
四行代码搭建服务器 var express = require('express'); var app = express(); //设定静态路径 所有请求优先在此路径查找 //不要把服务器配置JS文 ...
- [译]ASP.NET Core 2.0 路由引擎之网址生成
问题 如何在ASP.NET Core 2.0中由路由引擎来生成网址? 答案 新建一个空项目,修改Startup.cs文件,添加MVC服务和中间件: public void ConfigureServi ...
- 数据处理不等式:Data Processing Inequality
我是在差分隐私下看到的,新解决方案的可用性肯定小于原有解决方案的可用性,也就是说信息的后续处理只会降低所拥有的信息量. 那么如果这么说的话为什么还要做特征工程呢,这是因为该不等式有一个巨大的前提就是数 ...