在线程的常见方法一节中,已经接触过join()方法的使用。

  在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程将早于子线程结束。这时,如果主线程想等子线程执行完成才结束,比如子线程处理一个数据,主线程想要获得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

  join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。

  join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。(其实join()中调用的是join(0))

  join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。

源码如下:     方法join(long)的功能在内部是使用wait(long)来实现的,所以join(long)方法具有释放锁的特点。

    public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0; if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
} if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

1.一个简单的join方法的使用方法

package cn.qlq.thread.nine;

/**
* 线程类join()使用方法
*
* @author Administrator
*
*/
public class Demo1 extends Thread {
/**
* 更改线程名字
*
* @param threadName
*/
public Demo1(String threadName) {
this.setName(threadName);
} @Override
public void run() {
for (int i = 0; i < 2; i++) {
try {
Thread.sleep(1 * 500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-----" + i);
}
} public static void main(String[] args) {
Demo1 t1 = new Demo1("t1");
Demo1 t2 = new Demo1("t2");
Demo1 t3 = new Demo1("t3");
t1.start();
/**
* join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
* 程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
* 所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
*/
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (t2.isAlive()) {
System.out.println("t2 is alive");
} else {
System.out.println("t2 is not alive");
}
t2.start();
t3.start();
}
}

结果:

t1-----0
t1-----1
t2 is not alive
t3-----0
t2-----0
t2-----1
t3-----1

  方法x.join()的作用是使所属线程x 正常执行run()中的方法,而使得调用x.join()的线程处于无限期阻塞状态,等待x线程销毁后再继续执行线程z后面的代码。

  方法join()具有使线程排队运行的作用,有些类似于同步的运行效果。join()与synchronized的区别是:join在内部调用wait()方法进行等待,而synchronized关键字使用的是"对象监视器"原理作为同步。

2. join()与异常

  在join()过程中,如果当前线程被中断,则当前线程出现异常。(注意是调用thread.join()的线程被中断才会进入异常,比如a线程调用b.join(),a中断会报异常而b中断不会异常)

如下:threadB中启动threadA,并且调用其方法等待threadA完成,此时向threadB发出中断信号,会进入中断异常代码。

package cn.qlq.thread.nine;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 线程类join()使用方法--join中中断
*
* @author Administrator
*
*/
public class Demo2 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class); public static void main(String[] args) throws InterruptedException { final Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("threadA run");
while (true) { }
}
}, "threadA"); Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("threadB run");
threadA.start();
try {
threadA.join();
} catch (InterruptedException e) {
LOGGER.error("join error ,threadName - > {}", Thread.currentThread().getName(), e);
}
}
}, "threadB");
threadB.start(); // 向threadB发出中断信号
Thread.sleep(1 * 1000);
threadB.interrupt();
}
}

结果:

上面虽然进入异常代码块,但是线程仍然未停止 (因为threadA并没有抛出异常,所以仍然在存活),我们用jvisualVM查看线程:

3. 方法join(long)的使用

  方法join(long)是设定等待的时间。实际join()方法中调用的是join(0),当参数是0的时候表示无限期等待。

package cn.qlq.thread.nine;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* 线程类join()使用方法--join中中断
*
* @author Administrator
*
*/
public class Demo3 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); public static void main(String[] args) throws InterruptedException { final Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("threadA run");
while (true) { }
}
}, "threadA"); Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
LOGGER.info("threadB run");
threadA.start();
try {
threadA.join(2 * 1000);
} catch (InterruptedException e) {
LOGGER.error("join error ,threadName - > {}", Thread.currentThread().getName(), e);
}
LOGGER.info("threadB end");
}
}, "threadB");
threadB.start();
}
}

结果:(threadB线程等待threadA线程2秒钟之后两个线程开始并行运行)

4. 方法join(long)与sleep(long)的区别

  方法join(long)的功能在内部是使用wait(long)来实现的,所以join(long)方法具有释放锁的特点。

方法join(long)的源码如下:

    public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0; if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
} if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

  从源码可以看出,调用join(long)方法之后内部调用了wait()方法,因此会释放该对象锁。

(1)测试join(long)会释放锁

package cn.qlq.thread.nine;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/**
* join(long)释放锁
*
* @author Administrator
*
*/
public class Demo4 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class); public static void main(String[] args) throws InterruptedException {
LOGGER.info("main start");
final Demo4 demo4 = new Demo4(); // 启动demo4线程并且占用锁之后调用join(long)方法
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (demo4) {
LOGGER.info("进入同步代码块,threadName ->{} 占有 demo4 的锁", Thread.currentThread().getName());
demo4.start();
demo4.join(4 * 1000);
LOGGER.info("退出同步代码块,threadName ->{}", Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "threadA").start(); // 休眠2秒钟,调用对象的同步方法
Thread.currentThread().sleep(2 * 1000);
demo4.test2();
} @Override
public void run() {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public synchronized void test2() {
LOGGER.info("进入test2方法,占有锁,threadname->{}", currentThread().getName());
}
}

结果: (在休眠2秒钟后调用对象的同步方法能进入方法则证明join方法释放锁;而且在退出同步代码块之前打印了test信息则说明test2占用锁成功)

17:57:02 [cn.qlq.thread.nine.Demo4]-[INFO] main start
17:57:02 [cn.qlq.thread.nine.Demo4]-[INFO] 进入同步代码块,threadName ->threadA 占有 demo4 的锁
17:57:04 [cn.qlq.thread.nine.Demo4]-[INFO] 进入test2方法,占有锁,threadname->main
17:57:06 [cn.qlq.thread.nine.Demo4]-[INFO] 退出同步代码块,threadName ->threadA

(2)测试sleep(long)不会释放锁

package cn.qlq.thread.nine;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/**
* sleep(long)不会释放锁
*
* @author Administrator
*
*/
public class Demo5 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); public static void main(String[] args) throws InterruptedException {
LOGGER.info("main start");
final Demo5 demo4 = new Demo5(); // 启动demo4线程并且占用锁之后调用join(long)方法
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (demo4) {
LOGGER.info("进入同步代码块,threadName ->{} 占有 demo4 的锁", Thread.currentThread().getName());
demo4.start();
demo4.sleep(4 * 1000);
LOGGER.info("退出同步代码块,threadName ->{}", Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "threadA").start(); // 休眠2秒钟,调用对象的同步方法
Thread.currentThread().sleep(2 * 1000);
demo4.test2();
} @Override
public void run() {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public synchronized void test2() {
LOGGER.info("进入test2方法,占有锁,threadname->{}", currentThread().getName());
}
}

结果:(退出代码块才进入test2方法,证明sleep(long)没有释放锁)

17:59:30 [cn.qlq.thread.nine.Demo5]-[INFO] main start
17:59:30 [cn.qlq.thread.nine.Demo5]-[INFO] 进入同步代码块,threadName ->threadA 占有 demo4 的锁
17:59:34 [cn.qlq.thread.nine.Demo5]-[INFO] 退出同步代码块,threadName ->threadA
17:59:34 [cn.qlq.thread.nine.Demo5]-[INFO] 进入test2方法,占有锁,threadname->main

方法join()使用详解的更多相关文章

  1. SQL中的JOIN语法详解

    参考以下两篇博客: 第一个是 sql语法:inner join on, left join on, right join on详细使用方法 讲了 inner join, left join, righ ...

  2. Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量

    Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量 一丶线程的理论知识 什么是线程:    1.线程是一堆指令,是操作系统调度 ...

  3. C#多线程详解(一) Thread.Join()的详解

    bicabo   C#多线程详解(一) Thread.Join()的详解 什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程 ...

  4. .NET Excel导出方法及其常见问题详解

    摘要:.NET Excel导出方法及其常见问题详解. 一.Excel导出的实现方法 在.net 程序开发中,对于Excel文件的导出我们一共有三种导出方式: 利用文件输出流进行读写操作 这种方式的导出 ...

  5. cloudemanager安装时出现failed to receive heartbeat from agent问题解决方法(图文详解)

    不多说,直接上干货! 安装cdh5到最后报如下错误: 安装失败,无法接受agent发出的检测信号. 确保主机名称正确 确保端口7182可在cloudera manager server上访问(检查防火 ...

  6. cloudemanager安装时出现8475 MainThread agent ERROR Heartbeating to 192.168.30.1:7182 failed问题解决方法(图文详解)

    不多说,直接上干货!   问题详情 解决这个问题简单的,是因为有进程占用了.比如 # ps aux | grep super root ? Ss : : /opt/cm-/lib64/cmf/agen ...

  7. C#类、对象、方法和属性详解

    C#类.对象.方法和属性详解 一.相关概念: 1.对象:现实世界中的实体(世间万物皆对象) 2.类:具有相似属性和方法的对象的集合 3.面向对象程序设计的特点:封装 继承 多态 4.对象的三要素:属性 ...

  8. MySQL基础(三)多表查询(各种join连接详解)

    Mysql 多表查询详解 一.前言 二.示例 三.注意事项 一.前言 上篇讲到Mysql中关键字执行的顺序,只涉及了一张表:实际应用大部分情况下,查询语句都会涉及到多张表格 : 1.1 多表连接有哪些 ...

  9. $.ajax()方法所有参数详解;$.get(),$.post(),$.getJSON(),$.ajax()详解

    [一]$.ajax()所有参数详解 url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. type: 要求为String类型的参数,请求方式(post或get)默认为get.注 ...

随机推荐

  1. 【清北学堂2018-刷题冲刺】Contest 7

    Task 1:小奇采药 [问题描述]  小奇是只天资聪颖的喵,他的梦想是成为世界上最伟⼤的医师.  为此,他想拜喵星球最有威望的医师为师.  医师为了判断他的资质,给他出了⼀个难题.  医师把他带到⼀ ...

  2. bzoj1061 建图 + 最小费用流

    https://www.lydsy.com/JudgeOnline/problem.php?id=106152 对于一个点对上多个点,不太容易建图的时候,考虑逆向思考 申奥成功后,布布经过不懈努力,终 ...

  3. Symbol特殊用途

    1. Symbol.iterator 定义对象的迭代器 一般我们遍历一个对象用for...in es6新增了一个for...of 但是对象却不能用 因为对象没有“迭代器”,那么我们给它定制一个 有了迭 ...

  4. KVM安装启动虚拟机

    KVM定制OpenStack云主机 如何定制化OpenStack云主机?从去年10月份刚开始接触OpenStack,到现在也有一年了.虽说目前只是停留在用它,对它的一些组件简单的了解,但谈到制作出一个 ...

  5. 【MSSQL】SqlServer中delete语句表别名的问题

    1.一般情况下删除表数据的sql语句: delete from products 2.如果想给表起个别名再删除呢,就得像下面这样写了 delete products from products as ...

  6. python css盒子型 浮动

    ########################总结############### 块级标签能够嵌套某些块级标签和内敛标签 内敛标签不能块级标签,只能嵌套内敛标签 嵌套就是: <div> ...

  7. 使用rvm安装与切换Ruby

    列出已知的 Ruby 版本 rvm list known安装一个 Ruby 版本 rvm install 2.3.1 --disable-binary这里安装了最新的 2.2.0, rvm list ...

  8. c3p0配置之preferredTestQuery参数默认值探秘

    http://www.mchange.com/projects/c3p0/ c3p0的配置参数preferredTestQuery用于检测数据库连接测试,检测数据库是否能连接成功. Default: ...

  9. python3.x与2.x区别

    1.性能 Py3.0运行 pystone benchmark的速度比Py2.5慢30%.Guido认为Py3.0有极大的优化空间,在字符串和整形操作上可 以取得很好的优化结果. Py3.1性能比Py2 ...

  10. Docker 容器文件导出 - 六

    Docker 容器 导入导出 导入:import 导出:export 打tar包导出容器 nginx1 的文件系统: # docker export nginx1 > nginx1.tar.gz ...