在线程的常见方法一节中,已经接触过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. Calendar 类 案例 和 闰年的计算

    Calendar 类 是一个抽象类 getInstance()直接返回子类对象 直接调用 主要方法:get set add 代码如下: package cn.lijun.demo; import ja ...

  2. 关于Navicat远程连接远程服务器的mysql 报错问题

    我们连接远程服务器的mysql,如果出现问题,很大问题会出在服务器的端口和授权问题 首先我们通过 1:netstat -an|grep 3306 来查看mysql默认的端口3306是否开启,允许哪个i ...

  3. linux proc

    /proc文件系统下的多种文件提供的系统信息不是针对某个特定进程的,而是能够在整个系统范围的上下文中使用.可以使用的文件随系统配置的变化而变化. /proc/cmdline 这个文件给出了内核启动的命 ...

  4. Centos7使用kubeadm 安装多主高可用kubernets:v.1.11集群

    实验环境介绍: 本次实验环境是5个节点 3台master 2台node节点: k8smaster01 192.168.111.128 软件:etcd k8smaster haproxy keepali ...

  5. Redis Bgrewriteaof 命令

    一.背景 1. AOF: Redis的AOF机制有点类似于Mysql binlog,是Redis的提供的一种持久化方式(另一种是RDB),它会将所有的写命令按照一定频率(no, always, eve ...

  6. maven构建myeclipse 工程

    前提:安装maven完成后 mvn -version查看版本 一,新建WEB 工程  mvn archetype:generate -DgroupId={project-packaging} -Dar ...

  7. I/O模型之四:Java 浅析I/O模型(BIO、NIO、AIO、Reactor、Proactor)

    目录: <I/O模型之一:Unix的五种I/O模型> <I/O模型之二:Linux IO模式及 select.poll.epoll详解> <I/O模型之三:两种高性能 I ...

  8. netty的拆包和粘包

    第一种:自定义规则 比如说我们自己设定$_结尾的数据为一个整体. 看主要代码,大体不变,就多了几行代码.具体先看我上一篇的代码.这里只做修改 server端 b.childHandler(new Ch ...

  9. C#设计模式(13)——享元模式

    1.享元模式介绍 在软件开发中我们经常遇到多次使用相似或者相同对象的情况,如果每次使用这个对象都去new一个新的实例会很浪费资源.这时候很多人会想到前边介绍过的一个设计模式:原型模式,原型模式通过拷贝 ...

  10. 错误 3 未找到类型“sdk:Label”。请确保不缺少程序集引用并且已生成所有引用的程序集。

    错误: 错误 3 未找到类型“sdk:Label”.请确保不缺少程序集引用并且已生成所有引用的程序集. 错误 1 命名空间“http://schemas.microsoft.com/winfx/200 ...