join方法介绍

首先给出结论:t.join()方法只会使调用该方法的线程进入t对象的等待池,并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。
一、使用方式

join是Thread类的一个方法,启动线程后直接调用,例如:

Thread t = new AThread(); t.start(); t.join();
二、为什么要用join()方法

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

三、join方法的作用

在JDk的API里对于join()方法是:

join

public final void join() throws InterruptedException Waits for this thread to die. Throws: InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.

即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在主线程调用了join()方法,后面的代码只有等到子线程结束了才能执行。

四、实例

之前对于join()方法只是了解它能够使得t.join()中的t优先执行,当t执行完后才会执行其他线程。能够使得线程之间的并行执行变成串行执行。
public class TestJoin {

public static void main(String[] args) throws InterruptedException {
	// TODO Auto-generated method stub
	ThreadTest t1=new ThreadTest("A");
	ThreadTest t2=new ThreadTest("B");
	t1.start();
	t2.start();
} 

}

class ThreadTest extends Thread {
private String name;
public ThreadTest(String name){
this.name=name;
}
public void run(){
for(int i=1;i<=5;i++){
System.out.println(name+"-"+i);
}
}
}

运行结果:

A-1
B-1
B-2
B-3
A-2
B-4
A-3
B-5
A-4
A-5
可以看出A线程和B线程是交替执行的。
而在其中加入join()方法后(后面的代码都略去了ThreadTest类的定义)

package CSDN;
public class TestJoin {

public static void main(String[] args) throws InterruptedException {
	// TODO Auto-generated method stub
	ThreadTest t1=new ThreadTest("A");
	ThreadTest t2=new ThreadTest("B");
	t1.start();
	t1.join();
	t2.start();
}

}
运行结果:

A-1
A-2
A-3
A-4
A-5
B-1
B-2
B-3
B-4
B-5
显然,使用t1.join()之后,B线程需要等A线程执行完毕之后才能执行。需要注意的是,t1.join()需要等t1.start()执行之后执行才有效果,此外,如果t1.join()放在t2.start()之后的话,仍然会是交替执行,然而并不是没有效果,这点困扰了我很久,也没在别的博客里看到过。

为了深入理解,我们先看一下join()的源码。

/**
 * Waits for this thread to die.
 *
 * <p> An invocation of this method behaves in exactly the same
 * way as the invocation
 *
 * <blockquote>
 * {@linkplain #join(long) join}{@code (0)}
 * </blockquote>
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public final void join() throws InterruptedException {
    join(0);            //join()等同于join(0)
}
/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 *
 * @param  millis
 *         the time to wait in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
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);           //join(0)等同于wait(0),即wait无限时间直到被notify
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

可以看出,join()方法的底层是利用wait()方法实现的。可以看出,join方法是一个同步方法,当主线程调用t1.join()方法时,主线程先获得了t1对象的锁,随后进入方法,调用了t1对象的wait()方法,使主线程进入了t1对象的等待池,此时,A线程则还在执行,并且随后的t2.start()还没被执行,因此,B线程也还没开始。等到A线程执行完毕之后,主线程继续执行,走到了t2.start(),B线程才会开始执行。

举个例子,join是在main方法里被调用了。
然后main方法就持有了 join方法 的 这个锁。
然后join 方法里面调用了 wait方法。
这个过程的目的是让持有这个同步锁的线程进入等待。
那么谁持有了这个同步锁呢?
答案就是main方法,因为main方法调用了join方法。
main方法就持有 synchronized 标记的这个锁,谁持有这个锁谁就等待。
wait()方法只会让持有锁的线程进入等待
然后join方法执行完之后,不用想,JVM底层肯定执行了 notify的操作。

网上copy来的jvm代码

//一个c++函数:
void JavaThread::exit(bool destroy_vm, ExitType exit_type) ;

//这家伙是啥,就是一个线程执行完毕之后,jvm会做的事,做清理啊收尾工作
//里面有一个贼不起眼的一行代码,眼神不好还看不到的呢,就是这个:

ensure_join(this);

//翻译成中文叫 确保_join(这个);代码如下:

static void ensure_join(JavaThread* thread) {
Handle threadObj(thread, thread->threadObj());

ObjectLocker lock(threadObj, thread);

thread->clear_pending_exception();

java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);

java_lang_Thread::set_thread(threadObj(), NULL);

//thread就是当前线程main线程啊。
lock.notify_all(thread);

thread->clear_pending_exception();
}

notify_all 之后 join方法结束。
调用join()的线程又可以获取到cpu资源了,继续执行join()后面的代码。

此外,对于join()的位置和作用的关系,我们可以用下面的例子来分析

public class TestJoin {

public static void main(String[] args) throws InterruptedException {
	// TODO Auto-generated method stub
	System.out.println(Thread.currentThread().getName()+" start");
	ThreadTest t1=new ThreadTest("A");
	ThreadTest t2=new ThreadTest("B");
	ThreadTest t3=new ThreadTest("C");
	System.out.println("t1start");
	t1.start();
	System.out.println("t2start");
	t2.start();
	System.out.println("t2end");
	System.out.println("t3start");
	t3.start();
	System.out.println("t3end");
	System.out.println(Thread.currentThread().getName()+" end");
}

}
运行结果为

main start
t1start
t1end
t2start
t2end
t3start
t3end
A-1
A-2
main end
C-1
C-2
C-3
C-4
C-5
A-3
B-1
B-2
B-3
B-4
B-5
A-4
A-5
A、B、C和主线程交替运行。加入join()方法后

public class TestJoin {

public static void main(String[] args) throws InterruptedException {
	// TODO Auto-generated method stub
	System.out.println(Thread.currentThread().getName()+" start");
	ThreadTest t1=new ThreadTest("A");
	ThreadTest t2=new ThreadTest("B");
	ThreadTest t3=new ThreadTest("C");
	System.out.println("t1start");
	t1.start();
	System.out.println("t1end");
	System.out.println("t2start");
	t2.start();
	System.out.println("t2end");
	t1.join();
	System.out.println("t3start");
	t3.start();
	System.out.println("t3end");
	System.out.println(Thread.currentThread().getName()+" end");
}

}
第1次运行结果

main start
t1start
t1end
t2start
t2end
A-1
A-2
A-3
A-4
A-5
B-1
t3start
B-2
t3end
main end
B-3
B-4
B-5
C-1
C-2
C-3
C-4
C-5
第2次运行结果
main start
t1start
t1end
t2start
t2end
A-1
A-2
A-3
A-4
A-5
t3start
t3end
main end
C-1
C-2
C-3
C-4
C-5
B-1
B-2
B-3
B-4
B-5
第3次运行结果
main start
t1start
t1end
t2start
A-1
A-2
t2end
A-3
B-1
B-2
B-3
A-4
A-5
B-4
B-5
t3start
t3end
main end
C-1
C-2
C-3
C-4
C-5
多次实验可以看出,主线程在t1.join()方法处停止,并需要等待A线程执行完毕后才会执行t3.start(),然而,并不影响B线程的执行。
t.join()方法只会使调用该方法的线程进入t对象的等待池,并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。

猜你喜欢

转载自blog.csdn.net/u014207606/article/details/84943027