最近看多线程的时候发现对于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的更多相关文章

  1. SQL Server-聚焦IN VS EXISTS VS JOIN性能分析(十九)

    前言 本节我们开始讲讲这一系列性能比较的终极篇IN VS EXISTS VS JOIN的性能分析,前面系列有人一直在说场景不够,这里我们结合查询索引列.非索引列.查询小表.查询大表来综合分析,简短的内 ...

  2. 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. ...

  3. Nested Loops join时显示no join predicate原因分析以及解决办法

    本文出处:http://www.cnblogs.com/wy123/p/6238844.html 最近遇到一个存储过程在某些特殊的情况下,效率极其低效, 至于底下到什么程度我现在都没有一个确切的数据, ...

  4. c# Enumerable中Aggregate和Join的使用

    参考页面: http://www.yuanjiaocheng.net/ASPNET-CORE/asp.net-core-environment.html http://www.yuanjiaochen ...

  5. 超详细mysql left join,right join,inner join用法分析

    下面是例子分析表A记录如下: aID        aNum 1           a20050111 2           a20050112 3           a20050113 4   ...

  6. join Linq

    List<Publisher> Publishers = new List<Publisher>(); Publisher publish1 = new Publisher() ...

  7. mysql join 和left join 对于索引的问题

    今天遇到一个left join优化的问题,搞了一下午,中间查了不少资料,对MySQL的查询计划还有查询优化有了更进一步的了解,做一个简单的记录: select c.* from hotel_info_ ...

  8. BCL中String.Join的实现

    在开发中,有时候会遇到需要把一个List对象中的某个字段用一个分隔符拼成一个字符串的情况.比如在SQL语句的in条件中,我们通常需要把List<int>这样的对象转换为“1,2,3”这样的 ...

  9. [数据库基础]——图解JOIN

    阅读导航 一.概要 二.JOIN分类 三.JOIN分类详解 一.概要 JOIN对于接触过数据库的人,这个词都不陌生,而且很多人很清楚各种JOIN,还有很多人对这个理解也不是很透彻,这次就说说JOIN操 ...

  10. Spark join 源码跟读记录

    PairRDDFunctions类提供了以下两个join接口,只提供一个参数,不指定分区函数时默认使用HashPartitioner;提供numPartitions参数时,其内部的分区函数是HashP ...

随机推荐

  1. Python成长之路 — 字典

    一.字典的定义与创建 字典是Python中唯一内建的映射类型.你可以将其想象成书本的目录,章节名称代表"key",页码则代表"value".书本的目录本质上是也 ...

  2. threading多线程总结

    threading用于提供线程相关的操作,线程是应用程序中工作的最小单元.python当前版本的多线程库没有实现优先级.线程组,线程也不能被停止.暂停.恢复.中断. threading模块提供的类:  ...

  3. Azure 基础:Blob Storage

    Azure Storage 是微软 Azure 云提供的云端存储解决方案,当前支持的存储类型有 Blob.Queue.File 和 Table. 笔者在前文中介绍了 Table Storage 的基本 ...

  4. centOS下服务启动

    nginx应该在mongodb之后启动,也可以通过chkconfig <服务名> on将服务设置为开机自启动.具体命令如下 service mysql start service memc ...

  5. java知识点整理

    1 java 和Tomcat总结 脑图地址  (其中web 容器部分还需要继续完善,但是没找到相关文档) 跟着java Se 文档梳理了一下学习路线图(方便全面掌握要点,及时对自己查漏补缺),以及一些 ...

  6. ssh无密码登录远程主机

    方法:在客户端生成公/私钥对,将私钥文件保存在客户端,再将公钥文件上传到服务器端(远程主机) 1.在客户端生成公/私钥对 cb@cb251#ssh-keygen...cb@cb251#ls .ssh/ ...

  7. CSS动画属性性能详细介绍

    CSS动画属性会触发整个页面的重排relayout.重绘repaint.重组recomposite Paint通常是其中最花费性能的,尽可能避免使用触发paint的CSS动画属性,这也是为什么我们推荐 ...

  8. mysql GROUP_CONCAT获取分组的前几名

    比如说要获取班级的前3名,oracle 可以用 over partition by 来做.mysql就可以用GROUP_CONCAT  + GROUP BY + substring_index实现. ...

  9. 数据结构与算法(c++)——查找二叉树与中序遍历

    查找树ADT--查找二叉树 定义:对于树中的每个节点X,它的左子树中的所有项的值小于X中的项,而它的右子树中所有项的值大于X中的项. 现在给出字段和方法定义(BinarySearchTree.h) # ...

  10. 设置Ubuntu下adb 及 fastboot权限

    以普通用户登录linux,然后运行adb devices会提示权限不够: List of devices attached  ????????????    no permissions   这是因为 ...