一、join()介绍

join() 定义在Thread.java中。
join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。这句话可能有点晦涩,我们还是通过例子去理解:

// 主线程
public class Father extends Thread {
public void run() {
Son s = new Son();
s.start();
s.join();
...
}
}
// 子线程
public class Son extends Thread {
public void run() {
...
}
}

说明
     上面的有两个类Father(主线程类)和Son(子线程类)。因为Son是在Father中创建并启动的,所以,Father是主线程类,Son是子线程类。
     在Father主线程中,通过new Son()新建“子线程s”。接着通过s.start()启动“子线程s”,并且调用s.join()。在调用s.join()之后,Father主线程会一直等待,直到“子线程s”运行完毕;在“子线程s”运行完毕之后,Father主线程才能接着运行。 这也就是我们所说的“join()的作用,是让主线程会等待子线程结束之后才能继续运行”!

二、join()源码分析

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;
}
}
}

说明:
从代码中,我们可以发现。当millis==0时,会进入while(isAlive())循环;即只要子线程是活的,主线程就不停的等待。
我们根据上面解释join()作用时的代码来理解join()的用法!
问题:
     虽然s.join()被调用的地方是发生在“Father主线程”中,但是s.join()是通过“子线程s”去调用的join()。那么,join()方法中的isAlive()应该是判断“子线程s”是不是Alive状态;对应的wait(0)也应该是“让子线程s”等待才对。但如果是这样的话,s.join()的作用怎么可能是“让主线程等待,直到子线程s完成为止”呢,应该是让"子线程等待才对(因为调用子线程对象s的wait方法嘛)"?
答案:
     wait()的作用是让“当前线程”等待,而这里的“当前线程”是指当前在CPU上运行的线程。所以,虽然是调用子线程的wait()方法,但是它是通过“主线程”去调用的;所以,休眠的是主线程,而不是“子线程”!

三、join()示例

在理解join()的作用之后,接下来通过示例查看join()的用法。

package com.demo;

public class JoinTest {

    public static void main(String[] args){
try{
ThreadA t1 = new ThreadA("t1"); // 新建“线程t1” t1.start(); // 启动“线程t1”
t1.join(); // 将“线程t1”加入到“主线程main”中,并且“主线程main()会等待它的完成”
System.out.printf("%s finish\n", Thread.currentThread().getName());
}catch(InterruptedException e){
e.printStackTrace();
}
} static class ThreadA extends Thread{
public ThreadA(String name){
super(name);
} public void run(){
System.out.printf("%s start\n", this.getName()); // 延时操作
for(int i=0; i <1000000; i++)
; System.out.printf("%s finish\n", this.getName());
}
}
}

运行结果:

t1 start
t1 finish
main finish

结果说明
运行流程如图 
(01) 在“主线程main”中通过 new ThreadA("t1") 新建“线程t1”。 接着,通过 t1.start() 启动“线程t1”,并执行t1.join()。
(02) 执行t1.join()之后,“主线程main”会进入“阻塞状态”等待t1运行结束。“子线程t1”结束之后,会唤醒“主线程main”,“主线程”重新获取cpu执行权,继续运行。


一、join()的作用

join()是Thread类的一个方法。根据jdk文档的定义:

public final void join()throws InterruptedException: Waits for this thread to die.

join()方法的作用,是等待这个线程结束;但显然,这样的定义并不清晰。个人认为"Java 7 Concurrency Cookbook"的定义较为清晰:

join() method suspends the execution of the calling thread until the object called finishes its execution.

也就是说,t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,在某些情况下,主线程创建并启动了子线程,如果子线程中需要进行大量的耗时运算,主线程往往将早于子线程结束之前结束,如果主线程想等待子线程执行完毕后,获得子线程中的处理完的某个数据,就要用到join()方法了,即等待其它线程完成再结束main()主线程。

例子1:

package com.demo.join;

public class Test {

    public static class MyThread extends Thread {
@Override
public void run() { try {
System.out.println("我在子线程中睡眠1秒中");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public static void main(String[] args) throws InterruptedException {
MyThread myThread =new MyThread();
myThread.start();
myThread.join();
System.out.println("正常情况下肯定是我先执行完,但是加入join后,main主线程会等待子线程执行完毕后才执行");
}
}

输出结果:

我在子线程中睡眠1秒中
正常情况下肯定是我先执行完,但是加入join后,main主线程会等待子线程执行完毕后才执行

例子2:在主线程中开启一个子线程,用来计算1至100的和,然后在主线程中打印出来。

package com.demo.join;

public class JoinDemo {

    static int result = 0;

    public static void main(String[] args) {
Thread subThread = new Thread() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
result = result + i;
/*模拟耗时操作*/
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
subThread.start();
System.out.print("1到100求和结果等于:" + result);
}
}

程序运行结果:

1到100求和结果等于:0

出错了。程序运行进入Main()函数后,开启子线程subThread计算求和。此时主线程并没有停止,继续往下运行。子线程subThread运行耗时大约2秒,而主线程如出膛子弹迅速往下执行完毕。子线程此该还没有反应过来,主线程已经输出了结果。为了输出正确的结果,显而易见,必须让主线程等待子线程运行完毕再执行System.out.print。这时,轮到Thread.Join()出场了。 在subThread.start()和System.out.print(result)之间加上:

try {
subThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

程序运行结果:

1到100求和结果等于:5050

结果正确!

join()方法中可以设置值,即等待多久。比如上面如果把subThread.join()改为subThread.join(1000),就是告诉主线程等待子线程1秒钟后再继续运行。你可以这样修改后试着运行一下程序,这时程序输出的应该是0到5050间的一个值。

例子3:

package com.demo.join;

public class JoinTest {

    public static void main(String [] args) throws InterruptedException {
ThreadJoinTest t1 = new ThreadJoinTest("小明");
ThreadJoinTest t2 = new ThreadJoinTest("小东");
t1.start();
/**join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
*/
t1.join();
t2.start();
}
}
package com.demo.join;

public class ThreadJoinTest extends Thread{

    public ThreadJoinTest(String name){
super(name);
} @Override
public void run(){
for(int i=0;i<1000;i++){
System.out.println(this.getName() + ":" + i);
}
}
}

上面程序结果是先打印完小明线程,在打印小东线程。

上面注释也大概说明了join方法的作用:在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行,即join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。注意,这里调用的join方法是没有传参的,join方法其实也可以传递一个参数给它的,具体看下面的简单例子:

package com.demo.join;

public class JoinTest {

    public static void main(String [] args) throws InterruptedException {
ThreadJoinTest t1 = new ThreadJoinTest("小明");
ThreadJoinTest t2 = new ThreadJoinTest("小东");
t1.start();
/**join方法可以传递参数,join(10)表示main线程会等待t1线程10毫秒,10毫秒过去后,
* main线程和t1线程之间执行顺序由串行执行变为普通的并行执行
*/
t1.join(10);
t2.start();
}
}
package com.demo.join;

public class ThreadJoinTest extends Thread{

    public ThreadJoinTest(String name){
super(name);
} @Override
public void run(){
for(int i=0;i<1000;i++){
System.out.println(this.getName() + ":" + i);
}
}
}

上面代码结果是:程序执行前面10毫秒内打印的都是小明线程,10毫秒后,小明和小东程序交替打印。

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

二、join 与 start 调用顺序问题

上面的讨论大概知道了join的作用了,那么,如果 join在start前调用,会出现什么后果呢?先看下面的测试结果

package com.demo.join;

public class JoinTest {

    public static void main(String [] args) throws InterruptedException {
ThreadJoinTest t1 = new ThreadJoinTest("小明");
ThreadJoinTest t2 = new ThreadJoinTest("小东");
/*
*join方法可以在start方法前调用时,并不能起到同步的作用
*/
t1.join();
t1.start();
t2.start();
}
}
package com.demo.join;

public class ThreadJoinTest extends Thread{

    public ThreadJoinTest(String name){
super(name);
} @Override
public void run(){
for(int i=0;i<1000;i++){
System.out.println(this.getName() + ":" + i);
}
}
}

上面代码执行结果是:小明和小东线程交替打印。

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

三、主线程等待多个子线程的情况

要想主线程main等待若干线程结束之后再执行,需要先调用各个子线程的start()方法,在所有线程的start()方法执行完之后,再执行所有子线程的join()方法。若依次执行每个线程的start()和join()方法,则各个线程之间是同步的。举例如下:

package com.demo.join;

public class Test1 {

    public static void main(String[] args) {
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1 over");
}
}); Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2 over");
}
}); Thread t3 = new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3 over");
}
}); try {
t1.start();
t2.start();
t3.start(); t1.join();
t2.join();
t3.join(); } catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("都结束了"); }
}

运行结果:

2 over
1 over
3 over
都结束了

从结果中可以看到三个子线程能够并发执行。若想三个子线程会顺序同步的执行,需要改变join调用位置。

package com.demo.join;

public class Test1 {

    public static void main(String[] args) {
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1 over");
}
}); Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2 over");
}
}); Thread t3 = new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3 over");
}
}); try {
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join(); } catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("都结束了"); }
}

运行结果:

1 over
2 over
3 over
都结束了

从结果中可以看出,在多个子线程的情况下,若依次执行每个线程的start()和join()方法,则各个线程之间是同步的。

例子2:在主线程中建立了100个子线程,每个子线程使静态变量n增加10,如果在这100个子线程都执行完后输出n,这个n值应该是1000。

(1)先来看不加join()的情况:

package com.demo.join;

public class JoinThread extends Thread{

    public static volatile int n = 0;

    public void run(){
for (int i = 0; i < 10; i++) {
try{
// 为了使运行结果更随机,延迟3毫秒
sleep(3);
n++;
}
catch (Exception e){
}
}
} public static void main(String[] args) throws Exception{
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++) {
// 建立100个线程
threads[i] = new JoinThread();
} for (int i = 0; i < threads.length; i++) {
// 运行刚才建立的100个线程
threads[i].start();
} System.out.println("n=" + JoinThread.n);
}
}

运行结果:

n=71

这个运行结果可能在不同的运行环境下有一些差异,但一般n不会等于1000。从上面的结果可以肯定,这100个线程并未都执行完就将n输出了。

(2)先调用各个子线程的start()方法,再执行所有子线程的join()方法。

package com.demo.join;

public class JoinThread extends Thread{

    public static volatile int n = 0;

    public void run(){
for (int i = 0; i < 10; i++) {
try{
// 为了使运行结果更随机,延迟3毫秒
sleep(3);
n++;
//System.out.println(Thread.currentThread().getName()+"-------"+n);
}
catch (InterruptedException e){
e.printStackTrace();
}
}
} public static void main(String[] args) throws Exception{
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++) {
// 建立100个线程
threads[i] = new JoinThread();
} for (int i = 0; i < threads.length; i++) {
// 运行刚才建立的100个线程
threads[i].start();
} for (int i = 0; i < threads.length; i++) {
// 100个线程都执行完后继续
threads[i].join();
} System.out.println("n=" + JoinThread.n);
}
}

运行结果:

n=978

从结果可以看出,多个子线程可以并发执行,并且这100个线程都运行完才将n输出。但是由于volatile关键字只保证了操作的可见性,但是没办法保证对变量的操作的原子性,所以最后的结果仍是小于1000的一个数。

(3)依次执行每个子线程的start()和join()方法。

package com.demo.join;

public class JoinThread extends Thread{

    public static volatile int n = 0;

    public void run(){
for (int i = 0; i < 10; i++) {
try{
// 为了使运行结果更随机,延迟3毫秒
sleep(3);
n++;
//System.out.println(Thread.currentThread().getName()+"-------"+n);
}
catch (InterruptedException e){
e.printStackTrace();
}
}
} public static void main(String[] args) throws Exception{
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++) {
// 建立100个线程
threads[i] = new JoinThread();
} for (int i = 0; i < threads.length; i++) {
// 运行刚才建立的100个线程
threads[i].start();
threads[i].join();
} System.out.println("n=" + JoinThread.n);
}
}

运行结果:

n=1000

从结果中看出子线程之间是同步进行的,并且每次运行结果都会得到相同的结果:n=1000。这充分说明了这100个线程肯定是都执行完了,因此,n一定会等于1000。

四、join方法实现原理

有了上面的例子,我们大概知道join方法的作用了,那么,join方法实现的原理是什么呢?

其实,join方法是通过调用线程的wait方法来达到同步的目的的。例如,A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态,具体看下面的源码:

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方法的原理就是调用相应线程的wait方法进行等待操作的,例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。

注意:join还有join(millis)方法,可以加入等待时间,效果上类似sleep,但是还是有实际区别的。join底层是wait方法,所以它是会释放对象锁的,而sleep在同步的方法中是不释放对象锁的,只有同步方法执行完毕,其他线程才可以执行。

 

Java多线程(八)——join()的更多相关文章

  1. Java多线程中join、yield、sleep方法详解

    在Java多线程编程中,Thread类是其中一个核心和关键的角色.因此,对该类中一些基础常用方法的理解和熟练使用是开发多线程代码的基础.本篇主要总结一下Thread中常用的一些静态方法的含义及代码中的 ...

  2. Java多线程——<八>多线程其他概念

    一.概述 到第八节,就把多线程基本的概念都说完了.把前面的所有文章加连接在此: Java多线程——<一>概述.定义任务 Java多线程——<二>将任务交给线程,线程声明及启动 ...

  3. java多线程(八)-死锁问题和java多线程总结

    为了防止对共享受限资源的争夺,我们可以通过synchronized等方式来加锁,这个时候该线程就处于阻塞状态,设想这样一种情况,线程A等着线程B完成后才能执行,而线程B又等着线程C,而线程C又等着线程 ...

  4. JAVA多线程---wait() & join()

    题外话: interrupt()方法  并不能中断一个正常运行的线程!!! class myThread extends Thread{ @Override public void run(){ fo ...

  5. 转载:Java多线程中join方法的理解

    转载自:http://uule.iteye.com/blog/1101994 thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程.比如在线程B中调用了线程A ...

  6. Java多线程的join()

    假设在main线程里又起了一个thread1线程,在调用了thread1.start()之后: 如果在main线程里调用了thread1.join(),那么main线程将会block,直到thread ...

  7. Java多线程中join方法详解

    join()方法用于让当前执行线程等待join线程执行结束.其实现原理是不停的检查join线程是否存活,如果join线程存活则让当前线程永远等待. join()方法部分实现细节 while(isAli ...

  8. Java多线程中join方法的理解

    thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程.比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B. t.join( ...

  9. Java多线程学习——join方法的使用

    join在线程里面意味着“插队”,哪个线程调用join代表哪个线程插队先执行——但是插谁的队是有讲究了,不是说你可以插到队头去做第一个吃螃蟹的人,而是插到在当前运行线程的前面,比如系统目前运行线程A, ...

  10. java 多线程 Thread.join子线程结束父线程再运行;join(long):等待超时毫秒数

    Join的使用 目的:当子线程运行结束后,父线程才能再继续运行 /** * @ClassName ThreadJoinExample * @projectName: object1 * @author ...

随机推荐

  1. div中img依据不同分辨率居中显示,超出部分隐藏

    在做banner居中时 碰到的问题,知道可以用背景图实现居中显示,但是内心是想深究下的,故找到几种办法收集一下,后面两种真的是奇技淫巧 来着下面两处 https://www.zhihu.com/que ...

  2. iOS---------Xcode中添加预编译pch文件

    第一步:打开项目,com+N,将页面滑动最下面如图 第二步:创建pch文件 第三步:修改buildsetting配置文件       在搜索框里输入prefix搜索一下,比较好找      1.将Pr ...

  3. Stable Fur Generation on Mesh

    After tested the Maya 2015 XGen Grooming, we dropped it, that's really slow and unstable, totally no ...

  4. Java虚拟机(五)Java的四种引用级别

    1.前言 HotSpot采取了可达性分析算法用来判断对象是否被能被GC,无论是引用计算法还是可达性分析算法都是判断对象是否存在引用来判断对象是否存活.如果reference类型的数据中存储的数值代表的 ...

  5. python安装pbkdf2 遇到错误TypeError: __call__() takes exactly 2 arguments (1 given)

    python安装模块时遇到如下错误, import packaging.requirements File "/usr/lib/python2.7/site-packages/packagi ...

  6. 【软件需求工程与建模 - 小组项目】第6周 - 成果展示3 - 软件设计规格说明书V4.1

    成果展示3 - 软件设计规格说明书V4.1

  7. 线程ThreadDemo04

    package day190109; public class 线程ThreadDemo04 { public static void main(String[] args) throws Inter ...

  8. 一、Selenium 工作原理

    1.Selenium介绍 Selenium是用于测试Web应用程序用户界面UI的常用框架.端对端的功能测试.并且在一个多个浏览器中操作. 目前Seienium 组件主要包括Selenium IDE   ...

  9. 【文学文娱】《屌丝逆袭》-出任CEO、迎娶白富美、走上人生巅峰

    本文地址:http://www.cnblogs.com/aiweixiao/p/7759790.html 原文地址:(微信公众号) 原创 2017-10-30 微信号wozhuzaisi 程序员的文娱 ...

  10. input accept属性限制文件上传格式

    上传文件的类型:具体做法如下所示: 注意:accept属性可以限制上传格式,其有兼容性如下 <1>上传.csv格式的 <input text="file" acc ...