在线程的常见方法一节中,已经接触过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. tcpdump高级过滤

    一:查看帮助选项 tcpdump --help Usage: tcpdump [-aAbdDefhHIJKlLnNOpqStuUvxX#] [ -B size ] [ -c count ] [ -C ...

  2. IO以及file的一些基本方法

    IO定义:将内存的数据 持久化到设备上 这个动作称为输出 写 Output操作 把硬盘上的数据读取到内存 这个动作称为输入 读 Input 把上面这两种操作称为IO操作 File类的静态成员变量:pa ...

  3. 【.net】“Newtonsoft.Json”已拥有为“Microsoft.CSharp”定义的依赖项。

    #事故现场: “Newtonsoft.Json”已拥有为“Microsoft.CSharp”定义的依赖项. #事故原因: 安装的Newtonsoft.Json版本为11.0.2,版本过高,与Micro ...

  4. python 面向对象(经典作业讲解)

    #############################总结######################## # lst =['python%s期' %i for i in range(510)]# ...

  5. Hadoop记录-fair公平调度队列管理

    <?xml version="1.0"?> <allocations> <queue name="root"> <qu ...

  6. Linux拉你入门

    前言:为了做一个更优秀的程序猿,Linux是必不可少的,因此利用闲杂的时间来增加自己对Linux的认识 (一)关于Linux命令编(至于怎样安装vmvare这一个章节就先不介绍了) 1.基础命令 1. ...

  7. jQuery图片灯箱和视频灯箱

    在一些前端页面中经常需要文件上传,为了美观,我们经常做一个灯箱来显示我们选择的文件, 而不是简单的input标签. html 代码:这个是多图片上传 <div class="layui ...

  8. 细说shiro之一:shiro简介

    官网:https://shiro.apache.org/ 一. Shiro是什么Shiro是一个Java平台的开源权限框架,用于认证和访问授权.具体来说,满足对如下元素的支持: 用户,角色,权限(仅仅 ...

  9. vue2.0页面缓存和不缓存的方法

    // 模板中: <div class="home"> <keep-alive> <router-view v-if="$route.meta ...

  10. MySQL api

    今天看去年年中写的代码,留意到一个关键时刻能提高效率的api:on duplicate key update: 语法: INSERT INTO INSERT INTO g_iot_user_build ...