join和wait
最近看多线程的时候发现对于join的理解有些错误,在网上查了不少资料,根据自己的理解整理了一下,这里之所以把join和wait放在一起,是因为join的底层实现就是基于wait的,一并讲解更容易理解。
wait
了解join就先需要了解wait,wait是线程间通信常用的信号量,作用就是让线程暂时停止运行,等待其他线程使用notify来唤醒或者达到一定条件自己苏醒。
wait是一个本地方法,属于Object类,其底层实现是JVM内部实现,是基于monitor对象监视锁。
//本地方法
public final native void wait(long timeout) throws InterruptedException;
//参数有纳秒和毫秒
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
//存在纳秒,默认加一毫秒
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
//无参数,默认为0
public final void wait() throws InterruptedException {
wait(0);
}
根据源码可以发现,虽然wait有三个重载的方法,但是主要的还是wait(long timeout)这个本地方法,其他两个都是基于这个来封装的,由JVM底层源码不太好看到,我就以流程的形式来描述。
synchronized (this) {
System.out.println("A begin " + System.currentTimeMillis());
try {
wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A end " + System.currentTimeMillis());
}
- 上述代码在执行到wait(5000)时首先会释放当前占用的锁,并暂停线程。
- 在暂停的5秒内如果收到其他线程的notify()方法发来的信号,那么就再次尝试获取已经释放的锁
- 如果获取到那么就继续执行,没有就等待锁释放来竞争。
- 如果在5秒内未收到信号,那么到时间后就自动苏醒去尝试获取锁。
而对于时间的参数timeout需要注意的是,如果输入0不代表不暂停,而是需要特殊情况自己苏醒或者notify唤醒,这里有个特殊点,wait(0)是可以自己苏醒的。
public class Thread2 extends Thread{
private Thread1 a;
public Thread2(Thread1 a) {
this.a = a;
}
@Override
public void run() {
synchronized (a) {
System.out.println("B begin " + System.currentTimeMillis());
try {
a.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B end " + System.currentTimeMillis());
}
}
}
public class Thread1 extends Thread{
@Override
public void run() {
synchronized (this) {
System.out.println("A begin " + System.currentTimeMillis());
System.out.println("A end " + System.currentTimeMillis());
}
}
}
public class Main{
public static void main(String[] args) {
Thread1 thread = new Thread1();
Thread2 thread2 = new Thread2(thread);
thread2.start();
thread.start();
System.out.println("main end "+System.currentTimeMillis());
}
}
这个例子运行结果存在以下情况
B begin 1494995803564
main end 1494995803565
A begin 1494995803565
A end 1494995803565
B end 1494995803565
wait()在没有notify()情况下自动苏醒了,因此这里可以看到,当前情况下Thread.wait()等待过程中,如果Thread结束了,是可以自动唤醒的。这个会在join中被使用。
join
了解了wait的实现原理之后就可以来看join了,join是Thread类的方法,不是底层本地方法,这里可以看一下它的源码。
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
//参数判断<0,抛异常
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//参数为0,即join()
if (millis == 0) {
//当前线程存活,就调用wait(0),一直到调用join的线程结束再自动苏醒
while (isAlive()) {
wait(0);
}
//参数>0,调用wait(long millis)等待一段时间后自动唤醒
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
public final void join() throws InterruptedException {
join(0);
}
很明显,join的三个重载方法主要还是基于join(long millis)方法,因此我们主要关注这个方法,方法的处理逻辑如下
- 判断参数时间参数,如果参数小于0,抛出IllegalArgumentException("timeout value is negative")异常
- 参数等于0,判断调用join的线程(假设是A)是否存活,不存活就不执行操作,如果存活,就调用wait(0),阻塞join方法,等待A线程执行完在结束join方法。
- 参数大于0,判断调用join的A线程是否存活,不存活就不执行操作,如果存活,就调用wait(long millis),阻塞join方法,等待时间结束再继续执行join方法。
由于join是synchronized修饰的同步方法,因此会出现join(long millis)阻塞时间超过了millis的值。
public class Thread1 extends Thread{
@Override
public void run() {
synchronized (this) {
System.out.println("A begin " + System.currentTimeMillis());
try {
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A end " + System.currentTimeMillis());
}
}
}
public class Main{
public static void main(String[] args) {
Thread1 thread = new Thread1();
thread.start();
try {
thread.join(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("main end "+System.currentTimeMillis());
}
}
这个例子的运行结果是
A begin 1494996862054
A end 1494996867056
main end 1494996867056
main线程一定是最后执行完的,按照流程来说,1秒之后阻塞就结束了,main线程应该就可以开始执行了,但是这里有一个注意点,join(long millis)在执行millis>0的时候在wait(delay)之后还有一行代码,而上面代码1秒之后只是结束了wait方法,并没有执行完join方法。上面的例子,由于join的锁和thread的锁相同,在thread运行完之前,锁不会释放,那么导致join一直阻塞在最后一步无法结束,才会出现上面的情况。
join和wait的更多相关文章
- SQL Server-聚焦IN VS EXISTS VS JOIN性能分析(十九)
前言 本节我们开始讲讲这一系列性能比较的终极篇IN VS EXISTS VS JOIN的性能分析,前面系列有人一直在说场景不够,这里我们结合查询索引列.非索引列.查询小表.查询大表来综合分析,简短的内 ...
- SQL Server-聚焦NOT IN VS NOT EXISTS VS LEFT JOIN...IS NULL性能分析(十八)
前言 本节我们来综合比较NOT IN VS NOT EXISTS VS LEFT JOIN...IS NULL的性能,简短的内容,深入的理解,Always to review the basics. ...
- Nested Loops join时显示no join predicate原因分析以及解决办法
本文出处:http://www.cnblogs.com/wy123/p/6238844.html 最近遇到一个存储过程在某些特殊的情况下,效率极其低效, 至于底下到什么程度我现在都没有一个确切的数据, ...
- c# Enumerable中Aggregate和Join的使用
参考页面: http://www.yuanjiaocheng.net/ASPNET-CORE/asp.net-core-environment.html http://www.yuanjiaochen ...
- 超详细mysql left join,right join,inner join用法分析
下面是例子分析表A记录如下: aID aNum 1 a20050111 2 a20050112 3 a20050113 4 ...
- join Linq
List<Publisher> Publishers = new List<Publisher>(); Publisher publish1 = new Publisher() ...
- mysql join 和left join 对于索引的问题
今天遇到一个left join优化的问题,搞了一下午,中间查了不少资料,对MySQL的查询计划还有查询优化有了更进一步的了解,做一个简单的记录: select c.* from hotel_info_ ...
- BCL中String.Join的实现
在开发中,有时候会遇到需要把一个List对象中的某个字段用一个分隔符拼成一个字符串的情况.比如在SQL语句的in条件中,我们通常需要把List<int>这样的对象转换为“1,2,3”这样的 ...
- [数据库基础]——图解JOIN
阅读导航 一.概要 二.JOIN分类 三.JOIN分类详解 一.概要 JOIN对于接触过数据库的人,这个词都不陌生,而且很多人很清楚各种JOIN,还有很多人对这个理解也不是很透彻,这次就说说JOIN操 ...
- Spark join 源码跟读记录
PairRDDFunctions类提供了以下两个join接口,只提供一个参数,不指定分区函数时默认使用HashPartitioner;提供numPartitions参数时,其内部的分区函数是HashP ...
随机推荐
- 链表插入和删除,判断链表是否为空,求链表长度算法的,链表排序算法演示——C语言描述
关于数据结构等的学习,以及学习算法的感想感悟,听了郝斌老师的数据结构课程,其中他也提到了学习数据结构的或者算法的一些个人见解,我觉的很好,对我的帮助也是很大,算法本就是令人头疼的问题,因为自己并没有学 ...
- 使用DBeaver连接hive
介绍 在hive命令行beeline中写一些很长的查询语句不是很方便,查询结果也不是很友好,于是找了一个hive的客户端界面工具DBeaver,它也支持很多符合JDBC连接的数据库,例如MySQL.O ...
- 二阶段项目所遇问题 如何实现php向js传输数据
首先当前页面做了一个双处理的界面,就是有PhP也有JS的处理界面. 上一个传值界面是一个PHP的传值,结果,在当前页面的JS中也要用到上一界面传的值,这时发现,PHP与JS就像是两个互相孤立的小岛,根 ...
- 从《海贼王》的视角走进BAT的世界(百度/阿里/腾讯)
在伟大航路后半段--新世界,有三位君临天下的海上枭雄,就犹如海上皇帝一般,被人们称为"三皇",他们是--洛宾·里(Robin·Li),杰克·马(Jack·Ma)和波尼(pony)! ...
- Jmeter-WINDOWS下的配置部署
前提:已配置安装JDK环境及已部署TOMCAT. 解压apache-jmeter-2.9.zip文件至目录,我的是D:\Program Files目录. 点击我的电脑----属性----高级----环 ...
- R自带数据包
datasets(R自带数据包) 作者:王彦博 作品来源:百度百科 precip #长度为70的命名向量 euro #欧元汇率,长度为11,每个元素都有命名 landmasses #48个陆地的面积, ...
- mysql 发生系统错误 1067
最近要搞一个免安装版的mysql,原来的配置在d盘的my.ini如下 [client]port=3306default-character-set=utf8 [mysqld]port=3306char ...
- jqueryui autocomplete的使用与angular配合的小坑
刚开始在做搜索联想功能时,使用了jquery.autocomplete.js插件,当并不理想,首先插件老旧,也只适合老版的jquery.其次在数组中只能联想到首字母一样的数据,比如[12,23,222 ...
- java当中成员变量和局部变量的区别
1:成员变量定义在类中,整个类中都可以访问.2:局部变量定义在函数,语句,局部代码块中,只在所属的区域有效.3:成员变量存在于堆内存的对象中.4:局部变量存在于栈内存的方法中.5:成员变量随着对象的创 ...
- Linux下如何启动svn服务器
service svnserve start 启动服务 service svnserve stop 停止服务 service svnserve restart 重启服务 rpm -e --nodeps ...