Java Thread.join()

t.join() / t.join(long millis),当前线程里调用其它线程 t 的 join 方法,当前线程阻塞,但不释放对象锁,直到线程 t 执行完毕或者 millis 时间到,当前线程进入可运行状态。

t.join(); // 使调用线程 t 在此之前执行完毕
t.join(1000); // 等待 t 线程,等待时间是 1000 毫秒

JDK 源码:

public final void join() throws InterruptedException {  
    join(0);  
}

// 成员方法加了 synchronized 说明是 synchronized(this) 
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");  
    }  
    // 这里使用了 while 循环做判断,然后调用 wait 方法,所以说 join 方法的执行是完全通过 wait 方法实现的  
    // 等待时间为 0 的时候,就是无限等待,直到线程执行完了
    if (millis == 0) {  
        // 如果当前线程还存活的话,就等待  
        while (isAlive()) {  
            // 调用该线程的 join 方法的线程拿到锁之后进行等待,直到线程执行结束
            wait(0);  
        }  
    } else {  
        // 如果是等待的特定时间的话  
        while (isAlive()) {  
            long delay = millis - now;  
            if (delay <= 0) {  
                break;  
            }  
            wait(delay);  
            now = System.currentTimeMillis() - base;  
        }  
    }  
}  

从代码上看,如果线程被生成了,但还未被启动,调用它的 join() 方法是没有作用的,将直接继续向下执行

join 方法实现是通过 wait(Object 提供的方法)。当 main 线程调用 t.join() 之前,main 线程必须拥有线程对象 t 的锁,然后 main 线程调用 t.wait(time),直到等待时间 time 结束或者 t 线程执行完毕即 t.isAlive() == false

Example 1

public class JoinTest implements Runnable {  
      
    public static int a = 0;  
  
    public void run() {  
        for (int k = 0; k < 5; k++) {  
            a = a + 1;  
        }  
    }  
  
    public static void main(String[] args) throws Exception {  
        Runnable r = new JoinTest();  
        Thread t = new Thread(r);  
        t.start();        
        System.out.println(a);  
    }         
}  

程序的输出结果是 5 吗?答案是:有可能。其实你很难遇到输出 5 的时候,通常情况下都不是 5。为什么呢?当主线程 main 方法执行 System.out.println(a) 这条语句时,线程还没有真正开始运行,或许正在为它分配资源准备运行。因为为线程分配资源需要时间,而 main 方法执行完 t.start() 方法后继续往下执行 System.out.println(a),这时得到的结果是 a 还没有被改变的值 0 。怎样才能让输出结果为 5?其实很简单,join() 方法提供了这种功能。

public static void main(String[] args) throws Exception {  
    Runnable r = new JoinTest();  
    Thread t = new Thread(r);  
    t.start();        
    t.join();         // 加入 join()  
    System.out.println(a);  
}    

这个时候,程序输入结果始终为 5。

Example 2

class RunnableImpl implements Runnable {  
  
    public void run() {  
        try {  
            System.out.println("Begin sleep");  
            Thread.sleep(1000);  
            System.out.println("End sleep");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
  
    }  
}  

public class JoinTest {  
      
    public static void main(String[] args) {  
        Thread t = new Thread(new RunnableImpl());  
        t.start();  
        try {  
            t.join(1000);  
            System.out.println("joinFinish");  
        } catch (InterruptedException e) {  
            e.printStackTrace();       
        }  
    }  
}  

结果是:

Begin sleep
End sleep
joinFinish

当 main 线程调用 t.join 时,main 线程等待 t 线程,等待时间是 1000,如果 t 线程 Sleep 2000 呢

class RunnableImpl implements Runnable {  
  
    public void run() {  
        try {  
            System.out.println("Begin sleep");  
            Thread.sleep(2000);     // 原来为 1000  
            System.out.println("End sleep");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
  
    }  
}  

结果是:

Begin sleep
joinFinish
End sleep

也就是说 main 线程只等 1000 毫秒,不管 t 什么时候结束

Example 3

class CustomThread1 extends Thread {    
      
    public void run() {    
        String threadName = Thread.currentThread().getName();    
        System.out.println(threadName + " start.");    
        try {    
            for (int i = 0; i < 5; i++) {    
                System.out.println(threadName + " loop at " + i);    
                Thread.sleep(1000);    
            }    
            System.out.println(threadName + " end.");    
        } catch (Exception e) {    
            System.out.println("Exception from " + threadName + ".run");    
        }    
    }    
}    
  
class CustomThread extends Thread {    
    CustomThread1 t1;    
    public CustomThread(CustomThread1 t1) {            
        this.t1 = t1;    
    }    
    public void run() {    
        String threadName = Thread.currentThread().getName();    
        System.out.println(threadName + " start.");    
        try {    
            t1.join();    
            System.out.println(threadName + " end.");    
        } catch (Exception e) {    
            System.out.println("Exception from " + threadName + ".run");    
        }    
    }    
}    
  
public class JoinTestDemo {    
  
    public static void main(String[] args) {    
        String threadName = Thread.currentThread().getName();    
        System.out.println(threadName + " start.");    
        CustomThread1 t1 = new CustomThread1();    
        CustomThread t = new CustomThread(t1);    
        try {    
            t1.start();    
            Thread.sleep(2000);    
            t.start();    
            t.join();         // 下面会将此处注释掉    
        } catch (Exception e) {    
            System.out.println("Exception from main");    
        }    
        System.out.println(threadName + " end!");    
    }    
}   

结果:

main start. // main 方法所在的线程起动,但没有马上结束,因为调用 t.join(),所以要等到 t 结束了,此线程才能向下执行
[CustomThread1] Thread start. // 线程 CustomThread1 起动
[CustomThread1] Thread loop at 0 // 线程 CustomThread1 执行
[CustomThread1] Thread loop at 1 // 线程 CustomThread1 执行
[CustomThread] Thread start. // 线程 CustomThread 起动,但没有马上结束,因为调用 t1.join(),所以要等到 t1 结束了,此线程才能向下执行
[CustomThread1] Thread loop at 2 // 线程 CustomThread1 继续执行
[CustomThread1] Thread loop at 3 // 线程 CustomThread1 继续执行
[CustomThread1] Thread loop at 4 // 线程 CustomThread1 继续执行
[CustomThread1] Thread end. // 线程 CustomThread1 结束了
[CustomThread] Thread end. // 线程 CustomThread 在 t1.join() 阻塞处起动,向下继续执行的结果
main end! // 线程 CustomThread 结束,此线程在 t.join() 阻塞处起动,向下继续执行的结果

将上例中的 join 注释掉后结果为:

main start. // main 方法所在的线程起动,但没有马上结束,这里并不是因为 join 方法,而是因为 Thread.sleep(2000)
[CustomThread1] Thread start. // 线程 CustomThread1 起动
[CustomThread1] Thread loop at 0 // 线程 CustomThread1 执行
[CustomThread1] Thread loop at 1 // 线程 CustomThread1 执行
main end! // Thread.sleep(2000) 结束,虽然在线程 CustomThread 执行了 t1.join(),但这并不会影响到其他线程(这里 main 方法所在的线程)
[CustomThread] Thread start. // 线程 CustomThread 起动,但没有马上结束,因为调用 t1.join(),所以要等到 t1 结束了,此线程才能向下执行。
[CustomThread1] Thread loop at 2 // 线程 CustomThread1 继续执行
[CustomThread1] Thread loop at 3 // 线程 CustomThread1 继续执行
[CustomThread1] Thread loop at 4 // 线程 CustomThread1 继续执行
[CustomThread1] Thread end. // 线程 CustomThread1 结束了
[CustomThread] Thread end. // 线程 CustomThread 在 t1.join() 阻塞处起动,向下继续执行的结果

Example 4

main 线程调用 t.join 时,必须能够拿到线程 t 对象的锁,如果拿不到它是无法 wait 的,Example 2 中的
t.join(1000) 说明了 main 线程只等待 1 秒,但如果在它等待之前,其他线程获取了 t 对象的锁,它等待时间可不止 1 秒了

public class ThreadJoinTest {

    public static void main(String[] args) {
        Thread t = new Thread(new RunnableImpl());
        new ThreadTest(t).start();
        t.start();
        try {
            t.join(1000);           // main 线程等 1s
            System.out.println("join Finish");
        } catch (InterruptedException e) {
            e.printStackTrace();

        }
//        System.out.println("join Finish");
    }
}

class RunnableImpl implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("Thread Begin sleep " + System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println("Thread End sleep " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadTest extends Thread {
    Thread thread;
    public ThreadTest(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {
        holdThreadLock();
    }

    public void holdThreadLock() {
        //用当前的线程当做lock
        synchronized (thread) {
            System.out.println("ThreadTest getObjectLock " + System.currentTimeMillis());
            try {
                Thread.sleep(9 * 1000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            System.out.println("ThreadTest ReleaseObjectLock " + System.currentTimeMillis());
        }

    }
}

注意:Thread.sleep() 会使当前运行的线程交出执行权。Thread.sleep(0) 的作用,就是 “触发操作系统立刻重新进行一次 CPU 竞争”。

结果是:

ThreadTest getObjectLock 1519645264719
Thread Begin sleep 1519645264719
Thread End sleep 1519645266719
ThreadTest ReleaseObjectLock 1519645273720
join Finish

Thread Begin sleep 1519644950436
ThreadTest getObjectLock 1519644950436
Thread End sleep 1519644952437
ThreadTest ReleaseObjectLock 1519644959438
join Finish

在 main 方法中,通过 new ThreadTest(t).start() 实例化 ThreadTest 线程对象,它通过 synchronized (thread) ,获取线程对象 t 的锁,并 sleep(9000) 后释放,这就意味着,即使 main 方法 t.join(1000) 等待一秒钟,它也必须等待 ThreadTest 线程释放 t 锁后才能进入 wait 方法中。

注意:t.join(1000) 是让 main 线程等待 1000ms 或 t 死掉后执行,所以 t.join(1000) 和 sleep(9000) 是同时的,ThreadTest 的实际等待时间还是 9s

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容

  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,959评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,456评论 1 15
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,353评论 3 87
  • 莲约 来自心灵的约会 莲 安静如湖水 与鱼相伴 我 欣然而至 在莲最美的今天 等了多久了 远处小桥那么安静 近处绿...
    文竹君Fan阅读 278评论 0 5
  • 作业要求:1)定义接口--定义查车速的方法2)实现接口--根据车的类型-输出不同的车速3)定义抽象类、抽象方法 -...
    灼灼2015阅读 310评论 0 0