java 线程方法 join 的简单总结
虽然关于讨论线程 join 方法的博客已经很多了,不过个人感觉挺多都讨论得不够全面,所以我觉得有必要对其进行一个全面的总结。
一、作用
Thread 类中的 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();
}
}
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 方法其实也可以传递一个参数给它的,具体看下面的简单例子:
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();
}
}
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 前调用,会出现什么后果呢?先看下面的测试结果
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();
//Thread.yield();
t2.start();
}
}
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,那它也就无法同步了。
三、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 线程,从而达到同步的目的