`
uule
  • 浏览: 6303295 次
  • 性别: Icon_minigender_1
  • 来自: 一片神奇的土地
社区版块
存档分类
最新评论

Java多线程中join方法的理解

阅读更多

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

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

 

先上一段JDK中代码:

/**
	 *  Waits at most <code>millis</code> milliseconds for this thread to  
     * die. A timeout of <code>0</code> means to wait forever.	
	 */
	//此处A timeout of 0 means to wait forever 字面意思是永远等待,其实是等到t结束后。
	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() 方法是没有作用的,将直接继续向下执行

 

Join方法实现是通过wait(小提示:Object 提供的方法)。 当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁

 

Example1:

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() 方法提供了这种功能。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。

为 了证明如果不使用t.join()方法,主线程main方法的System.out.println(a);语句将抢先执行,我们可以在main方法中加入一个循环,这个循环用来延长main方法执行的时间,循环次数将严重取决于机器性能。如果循环次数得当,我们也可以看到a的输出结果是5。

public static void main(String[] args) throws Exception {
		Runnable r = new JoinTest();
		Thread t = new Thread(r);
		t.start();		
		//t.join(); //加入join()
			/*
			 注意循环体内一定要有实际执行语句,否则编译器或JVM可能优化掉你的这段代码,视这段代
			 码为无效。            
			*/
			for (int i=0; i<300; i++) {				
				System.out.print(i);
			}
			System.out.println();
		System.out.println(a);
	}		

 经自己测试,最后a一直是5.

本例参考:http://agio.iteye.com/blog/210600

 

Example2:join(n)

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什么时候结束.

参考:http://blog.csdn.net/FG2006/archive/2011/05/04/6393768.aspx

 

Example3:

 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(); //在代碼2里,將此處注釋掉  
         } 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注释掉:

 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方法所在的线程起动,但没有马上结束,这里并不是因为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();阻塞处起动,向下继续执行的结果

本例参考:http://blog.csdn.net/bzwm/archive/2009/02/12/3881392.aspx

 

Example4

main 线程调用t.join时,必须能够拿到线程t对象的锁,如果拿不到它是无法wait的 ,刚开的例子t.join(1000)不是说明了main线程等待1 秒,如果在它等待之前,其他线程获取了t对象的锁,它等待时间可不就是1毫秒了

class RunnableImpl implements Runnable {

    public void run() {
        try {
            System.out.println("Begin sleep");
            Thread.sleep(2000);
            System.out.println("End sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
 
class ThreadTest extends Thread {

    Thread thread;

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

    @Override
    public void run() {
    	synchronized (thread) {
            System.out.println("getObjectLock");
            try {
                Thread.sleep(9000);
            } catch (InterruptedException ex) {
             ex.printStackTrace();
            }
            System.out.println("ReleaseObjectLock");
        }
    }
}
 
public class JoinTest {
	    public static void main(String[] args) {
	        Thread t = new Thread(new RunnableImpl());
	       new ThreadTest(t).start();
	        t.start();
	        try {
	            t.join();
	            System.out.println("joinFinish");
	        } catch (InterruptedException e) {
	            e.printStackTrace();	     
	        }
	    }
}

 结果:
getObjectLock
Begin sleep
End sleep
ReleaseObjectLock
joinFinish

 

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

例子参考Example2来源.

分享到:
评论
12 楼 StartHere2012 2016-12-27  
StartHere2012 写道
shadowred 写道
"在main方法中 通过new  ThreadTest(t).start()实例化 ThreadTest 线程对象, 它 通过 synchronized  (thread) ,获取线程对象t的锁,并Sleep(9000)后释放,这就意味着,即使main方法t.join(1000)等待一秒钟,它必须等待ThreadTest 线程释放t锁后才能进入wait方法中,它实际等待时间是9000+1000ms。"

实际等待时间还是约9ms,“t.join(1000);”和“Sleep(9000)”是同时的,所以总时间上不会是和的结果。


再次验证:执行9000ms还是9000+1000ms,取决于join执行到还是ThreadTest的run先执行到,如果join先执行到,为9000ms,如果ThreadTest先执行到则是9000+1000ms



9000ms和9000ms+1000ms的差异在于:
如果join先获取到锁,join中记录启动时间now,进入wait,ThreadTest启动获取到锁,ThreadTest休眠9000ms,唤醒join,若join的线程结束了,则join退出,总共时间为9000ms,若线程没结束,则唤醒时间-启动时间也大于1000ms,也退出;所以这种情况下都是9000ms;

如果ThreadTest先获取到锁,休眠9000ms,回到主线程执行join,如join的线程结束,则join也结束,总共时间为9000ms;若join线程未结束(将t的sleep 时间改为9500ms或者20000ms),两种情况,若t的执行时间 > 9000ms+1000ms,则总时间为9000ms+1000ms,否则为t的执行时间;

thread.join的时间跟t的执行时间有关,是因为源码中的isAlive导致的
11 楼 StartHere2012 2016-12-27  
shadowred 写道
"在main方法中 通过new  ThreadTest(t).start()实例化 ThreadTest 线程对象, 它 通过 synchronized  (thread) ,获取线程对象t的锁,并Sleep(9000)后释放,这就意味着,即使main方法t.join(1000)等待一秒钟,它必须等待ThreadTest 线程释放t锁后才能进入wait方法中,它实际等待时间是9000+1000ms。"

实际等待时间还是约9ms,“t.join(1000);”和“Sleep(9000)”是同时的,所以总时间上不会是和的结果。


再次验证:执行9000ms还是9000+1000ms,取决于join执行到还是ThreadTest的run先执行到,如果join先执行到,为9000ms,如果ThreadTest先执行到则是9000+1000ms
10 楼 StartHere2012 2016-12-27  
louistz 写道
shadowred 写道
shadowred 写道
"在main方法中 通过new  ThreadTest(t).start()实例化 ThreadTest 线程对象, 它 通过 synchronized  (thread) ,获取线程对象t的锁,并Sleep(9000)后释放,这就意味着,即使main方法t.join(1000)等待一秒钟,它必须等待ThreadTest 线程释放t锁后才能进入wait方法中,它实际等待时间是9000+1000ms。"

实际等待时间还是约9ms,“t.join(1000);”和“Sleep(9000)”是同时的,所以总时间上不会是和的结果。


t.join(1000)是让main线程等待1000ms或t死掉后执行。


这两种理解的关键在于:
“进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程”
‘锁对象’和‘其他线程’是同一个对象时,这个线程是否得到了执行?


必须获得锁才能执行;
就楼主这个,虽然ThreadTest先start了,但是应该是先执行到t.join,t中记录执行时间,然后释放锁,等待唤醒;然后ThreadTest得到锁,sleep 9000ms,然后唤醒
the_small_base_ 写道
当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程


【wait不是释放当前线程的锁,并加入阻塞队列,等待notify或者notifyall将其唤醒】
是笔误了吗?


wait确实是释放,notify或者notifyall是唤醒


9 楼 StartHere2012 2016-12-26  
shadowred 写道
"在main方法中 通过new  ThreadTest(t).start()实例化 ThreadTest 线程对象, 它 通过 synchronized  (thread) ,获取线程对象t的锁,并Sleep(9000)后释放,这就意味着,即使main方法t.join(1000)等待一秒钟,它必须等待ThreadTest 线程释放t锁后才能进入wait方法中,它实际等待时间是9000+1000ms。"

实际等待时间还是约9ms,“t.join(1000);”和“Sleep(9000)”是同时的,所以总时间上不会是和的结果。


亲测试,确实是9s左右,不知楼主不看评论吗?有错也不改,误人子弟!
8 楼 the_small_base_ 2016-04-20  
当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程


【wait不是释放当前线程的锁,并加入阻塞队列,等待notify或者notifyall将其唤醒】
是笔误了吗?
7 楼 wslk857208 2016-02-28  
紫风GG 写道
http://www.baidu.com/

神经病啊你!!!!!!
6 楼 louistz 2015-06-08  
shadowred 写道
shadowred 写道
"在main方法中 通过new  ThreadTest(t).start()实例化 ThreadTest 线程对象, 它 通过 synchronized  (thread) ,获取线程对象t的锁,并Sleep(9000)后释放,这就意味着,即使main方法t.join(1000)等待一秒钟,它必须等待ThreadTest 线程释放t锁后才能进入wait方法中,它实际等待时间是9000+1000ms。"

实际等待时间还是约9ms,“t.join(1000);”和“Sleep(9000)”是同时的,所以总时间上不会是和的结果。


t.join(1000)是让main线程等待1000ms或t死掉后执行。


这两种理解的关键在于:
“进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程”
‘锁对象’和‘其他线程’是同一个对象时,这个线程是否得到了执行?
5 楼 shadowred 2015-06-03  
shadowred 写道
"在main方法中 通过new  ThreadTest(t).start()实例化 ThreadTest 线程对象, 它 通过 synchronized  (thread) ,获取线程对象t的锁,并Sleep(9000)后释放,这就意味着,即使main方法t.join(1000)等待一秒钟,它必须等待ThreadTest 线程释放t锁后才能进入wait方法中,它实际等待时间是9000+1000ms。"

实际等待时间还是约9ms,“t.join(1000);”和“Sleep(9000)”是同时的,所以总时间上不会是和的结果。


t.join(1000)是让main线程等待1000ms或t死掉后执行。
4 楼 shadowred 2015-06-03  
"在main方法中 通过new  ThreadTest(t).start()实例化 ThreadTest 线程对象, 它 通过 synchronized  (thread) ,获取线程对象t的锁,并Sleep(9000)后释放,这就意味着,即使main方法t.join(1000)等待一秒钟,它必须等待ThreadTest 线程释放t锁后才能进入wait方法中,它实际等待时间是9000+1000ms。"

实际等待时间还是约9ms,“t.join(1000);”和“Sleep(9000)”是同时的,所以总时间上不会是和的结果。
3 楼 oujunfeng 2014-06-24  
对 多线程的 join 又有了一个新的认识
不错
2 楼 紫风GG 2014-02-28  
http://www.baidu.com/
1 楼 u011444368 2014-01-18  
很好的一篇文章  

相关推荐

    Java线程中wait,await,sleep,yield,join用法总结.pdf

    Java线程中wait、await、sleep、yield、join用法汇总,文章里面总结了这些关键字的用法,并且里面带有源码帮助分析用法,此一文就可以理解这些关键字用法,推荐拥有

    Java线程中yield与join方法的区别

    虽然我个人认为我们当中很少有人能真正获得机会开发复杂的多线程应用(在过去的七年中,我得到了一个机会),但是理解多线程对增加你的信心很有用。之前,我讨论了一个wait()和sleep()方法区别的问题,这一次,我将会...

    Java高级程序设计-多线程(二).pptx

    本章内容 掌握同步代码块的使用 掌握同步方法的使用 理解线程死锁 掌握 ThreadLocal 类的使用 使用多线程模拟猴子采花 使用同步方法模拟购票 使用多线程模拟购物订单生成 使用 ThreadLocal 类模拟银行取款 Java高级...

    BAT面试官有点懵系列,Java多线程你真的理解透彻了吗?带你玩转一次多线程!Let’s go!别再out了!

    文章目录神标题引入线程和进程多线程的优势线程创建方式继承Thread类来创建和启动实现Runnable接口重写run方法创建线程类使用 Callable 和 Future 创建线程三种创建线程方式做出对比线程生命周期线程控制join线程...

    Java线程

    同一个进程中的多个线程之间可以并发执行 在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收gc集线程。每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作...

    java二叉树算法源码-JavaCore:Java核心知识。集合框架、JVM机制、多线程与并发框架、网络协议、SSM框架、MySQL、分布式、

    三、:locked_with_key:Java多线程与并发框架:unlocked: Java多线程与并发框 (第 13 篇) 深入理解:Fork/Join框架 Java多线程与并发框 (第 14 篇) 深入理解:原子操作 Java多线程与并发框 (第 15 篇) 深入理解...

    JAVA基础课程讲义

    JAVA中如何实现多线程(重点!!) 168 通过继承Thread类实现多线程 168 通过Runnable接口实现多线程 169 线程状态和sleep/yield/join/stop/destroy方法 170 新生状态 170 就绪状态 170 运行状态 170 死亡状态 170 ...

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    【多线程】Java四种线程池的创建方法 83 【多线程】线程池原理和运行机制 83 【多线程】线程池对任务的处理 85 【多线程】线程池的状态 86 线程池的状态说明 86 各个状态之间的转换 86 【多线程】什么是线程池?如果...

    疯狂JAVA讲义

    第1章 Java概述 1 1.1 Java语言的发展简史 2 1.2 Java的竞争对手及各自优势 4 1.2.1 C#简介和优势 4 1.2.2 Ruby简介和优势 4 1.2.3 Python的简介和优势 5 1.3 Java程序运行机制 5 1.3.1 高级语言的运行机制 6...

    Java并发编程原理与实战

    了解多线程所带来的安全风险.mp4 从线程的优先级看饥饿问题.mp4 从Java字节码的角度看线程安全性问题.mp4 synchronized保证线程安全的原理(理论层面).mp4 synchronized保证线程安全的原理(jvm层面).mp4 单例问题...

    龙果java并发编程完整视频

    第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 ...

    java范例开发大全源代码

     1.1 理解Java 2  1.2 搭建Java所需环境 3  1.2.1 下载JDK 3  1.2.2 安装JDK 4  1.2.3 配置环境 5  1.2.4 测试JDK配置是否成功 7  实例1 开发第一个Java程序 7  第2章 Java基础类型与运算符...

    java范例开发大全

    实例232 多线程同步方法的实例 436 实例233 ATM存取一体机(线程同步互斥) 437 实例234 我的钱哪里去了 440 实例235 门锁打不开了(死锁) 444 实例236 门锁终于被打开了(解决死锁) 446 实例237 一个死锁的例子 ...

    Java范例开发大全 (源程序)

     1.1 理解Java 2  1.2 搭建Java所需环境 3  1.2.1 下载JDK 3  1.2.2 安装JDK 4  1.2.3 配置环境 5  1.2.4 测试JDK配置是否成功 7  实例1 开发第一个Java程序 7  第2章 Java基础类型与运算符(教学...

    龙果 java并发编程原理实战

    第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 ...

    Java范例开发大全(全书源程序)

    实例232 多线程同步方法的实例 436 实例233 ATM存取一体机(线程同步互斥) 437 实例234 我的钱哪里去了 440 实例235 门锁打不开了(死锁) 444 实例236 门锁终于被打开了(解决死锁) 446 实例237 一个死锁的...

    免费超全面的Java基础类型,容器,并发,IO流,面向对象,Web编程等代码总结

    理解Java中对象基础Object类 基本数据类型,核心点整理 特殊的String类,以及相关扩展API 日期与时间API详解 流程控制语句,和算法应用 函数式编程概念和应用 集合容器 基于分析列表集合源码体系 基于分析地图集合...

    汪文君高并发编程实战视频资源下载.txt

    │ 高并发编程第一阶段26讲、多线程下的生产者消费者模型,以及详细介绍notifyAll方法.mp4 │ 高并发编程第一阶段27讲、wait和sleep的本质区别是什么,深入分析(面试常见问题).mp4 │ 高并发编程第一阶段28讲、...

    java范例开发大全(pdf&源码)

    实例232 多线程同步方法的实例 436 实例233 ATM存取一体机(线程同步互斥) 437 实例234 我的钱哪里去了 440 实例235 门锁打不开了(死锁) 444 实例236 门锁终于被打开了(解决死锁) 446 实例237 一个死锁的例子 ...

Global site tag (gtag.js) - Google Analytics