Java多线程中的 join()方法浅谈 及 面试题

鉴于 网上许多博客对于 join()方法 只是从底层源码分析看了个大概,我没能真正理解。但直到看完这三篇文章,有一种豁然开朗的感觉。所以自己在前辈的基础上总结一下 join()方法。

文1:https://blog.csdn.net/lyzx_in_csdn/article/details/79676708

文2:https://blog.csdn.net/chenkaibsw/article/details/80912878

文3(豁然开朗):https://blog.csdn.net/u013425438/article/details/80205693#commentsedit

 

join方法的真正含义:

在线程A中  调用 线程B对象.join( )方法:1.  如果线程B 是可运行态,则线程A阻塞自己运行,直到线程B 运行结束;2. 如果线程B 不是可运行状态,则继续执行线程A,并不会执行线程B.。      

 

 

通过代码去感受

实验1

//下面自己定义两个线程。
public class Thread1 extends Thread{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<11;i++) {
			System.out.println(Thread.currentThread().getName() + "------" + i);
		}
	}
}


public class Thread2 implements Runnable {

	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<11;i++) {
			System.out.println(Thread.currentThread().getName() +"----- " + i);
		}
	}

}



//测试类
public class JoinTest {

	public static void main(String[] args) {

        //给t1 t2线程取名字  方便对于执行结果的观察
		Thread t1 = new Thread(new Thread1(), "Thread---t1--");
		Thread t2 = new Thread(new Thread2(), "Thread---t2--");

		t1.start();

		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		t2.start();

	}
}

 运行结果:

执行多次后,运行结果均为

Thread---t1--------0
Thread---t1--------1
Thread---t1--------2
Thread---t1--------3
Thread---t1--------4
Thread---t1--------5
Thread---t1--------6
Thread---t1--------7
Thread---t1--------8
Thread---t1--------9
Thread---t1--------10
Thread---t2------- 0
Thread---t2------- 1
Thread---t2------- 2
Thread---t2------- 3
Thread---t2------- 4
Thread---t2------- 5
Thread---t2------- 6
Thread---t2------- 7
Thread---t2------- 8
Thread---t2------- 9
Thread---t2------- 10

 

实验2

//Thread1 Thread2 代码并没有改变   省略不写
public class JoinTest {

	public static void main(String[] args) {

		Thread t1 = new Thread(new Thread1(), "Thread---t1--");
		Thread t2 = new Thread(new Thread2(), "Thread---t2--");

		t1.start();
		t2.start();
		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

运行结果:

执行多次,发现 t1线程  与t2 线程交替执行。

Thread---t1--------0
Thread---t1--------1
Thread---t1--------2
Thread---t2------- 0
Thread---t1--------3
Thread---t2------- 1
Thread---t2------- 2
Thread---t2------- 3
Thread---t2------- 4
Thread---t2------- 5
Thread---t2------- 6
Thread---t2------- 7
Thread---t2------- 8
Thread---t2------- 9
Thread---t2------- 10
Thread---t1--------4
Thread---t1--------5
Thread---t1--------6
Thread---t1--------7
Thread---t1--------8
Thread---t1--------9
Thread---t1--------10

 

对于实验一 与实验二 运行结果的差异,是由于 t1.join() 加入的位置不同。

 

实验1 中 ti.join() 加在了 t2.start() 之前,也就是 主线程main 在调用t1线程的 join() 方法时 ,t2线程并没有开启。

 

而在实验2中 t1.join() 放在了 t2.start() 后面,也就是说 主线程在调用 ti线程的 join()方法之前,已经开启t1 、t2两个线程。

开启后的t2线程将与t1线程、主线程、并行执行。而主线程调用了 t1的join()方法,对已经处于运行状态的t2线程并没有影响,阻塞的是调用t1的join()方法的主线程。在t1线程没有运行结束之前,主线程将一直等待。直到t1线程终止 当线程终止时,t1线程才会调用线程自身的notifyAll()方法,通知所有等待在该线程对象上的线程运行。

运行流程图(当只有主线程 和 t1线程时)

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),即调用 t1线程的主线程wait无限时间直到被notify
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

 

其中核心代码如下:

while (isAlive()) {        //调用join方法线程是运行时状态
      wait(0);             //进入等待
 }

isAlive()方法下面会做详细的介绍,先看wait(0),wait(0)是什么意思呢,查看下面wait()方法源码,其实wait()方法就是调用了wait(0)方法实现的,wait(0)就是让其一直等待。到这里会发现,其实join方法本质就是利用上面的线程实例作为对象锁的原理,当线程终止时,会调用线程自身的notifyAll()方法,通知所有等待在该线程对象上的线程的运行。


while(isAlive)语句的作用

有下面的一段简单代码:

代码实现:

public class HighConcurrency {
	 
    // 1.现在有T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
        final Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 引用t1线程,等待t1线程执行完
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2");
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 引用t2线程,等待t2线程执行完
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3");
            }
        });
        t3.start();//这里三个线程的启动顺序可以任意,大家可以试下!
        t2.start();
    }
}

程序输出:

t2
t3

首先执行了t3.start(),在t3线程里的run方法里执行了t2.join(),此时有两种情况,可能还没有执行t2.start(),t2处于初始状态,也有可能执行了t2.start(),t2处于运行时状态,所以看到这里就明白了,join源码中while(isAlive()),其实相当于while(this.isAlive())就相当于判断这里的t2是不是已经是不是运行状态(有没有调用start方法)。这么设计是为了防止t2线程没有运行,此时t3线程如果直接执行wait(0)方法,那么将会一直等待下去,造成代码卡死。

为了验证,代码改为:

        t3.start();
        try {
		Thread.sleep(10);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
        t2.start();

此时输出

t3
t2

 

分析:将t2.start()和t3.start()之间的时间间隔变长,这样在t3线程中不会执行t2.join()时,保证了t2时处于初始状态还不是运行状态,此时while(isAlive())不成立,不会执行wait(0)方法,所以t3线程不会等待,会先输出t3。

然后再更改代码如下,保证在执行t2.join()时,t2.start()已经执行,t2已经处于运行状态:

public class HighConcurrency {
	
    public static void main(String[] args) {
 
        final Thread t2 = new Thread(new Runnable() {
 
            @Override
            public void run() {         
                System.out.println("t2");
            }
        });
        Thread t3 = new Thread(new Runnable() {
 
            @Override
            public void run() {
                try {
                	Thread.sleep(10);    //保证此时t2已经是运行时状态了
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3");
            }
        });
        t3.start();
        t2.start();
    }
}


此时输出

t2
t3

 分析:在t3线程中执行t2.join()方法前先执行了sleep(10)方法,保证在执行t2.join()时,t2已经是运行时状态了,所以此时t3会执行wait(0)方法等待,直到t2先执行完,t3再继续执行,所以输出t2 t3

 

 

join()的作用是让“主线程”等待“子线程”结束之后才能继续运行

面试题:

 现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行

package Demo0423JoinTest;

/**
 * join<p>
 * 面试题
 * join()的作用是让“主线程”等待“子线程”结束之后才能继续运行
 *
 *  现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
 */
public class JoinTest02 {
    public static void main(String[] args) throws InterruptedException {
        Thread t3 = new Thread(new MyThread3());
        t3.setName("线程3");
        t3.start();
        t3.join();


        System.out.println(Thread.currentThread().getName()+"停止");
    }

}

class MyThread1 extends Thread{

    @Override
    public void run() {
        try {
            //t1启动
            System.out.println(Thread.currentThread().getName()+"启动");

            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"停止");
        }
    }

}

class MyThread2 extends Thread{

    @Override
    public void run() {
        try {
            //启动t1,让t2等待t1执行完成
            Thread t1 = new Thread(new MyThread1());
            t1.setName("线程1");
            t1.start();
            t1.join();

            //t2启动
            System.out.println(Thread.currentThread().getName()+"启动");

            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"停止");
        }
    }

}

class MyThread3 extends Thread{

    @Override
    public void run() {
        try {
            //启动t2,让t3等待t2执行完成
            MyThread2 t2 = new MyThread2();
            t2.setName("线程2");
            t2.start();
            t2.join();

            //启动t3
            System.out.println(Thread.currentThread().getName()+"启动");

            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName()+"停止");
        }
    }

}
运行结果:

线程1启动
线程1停止
线程2启动
线程2停止
线程3启动
线程3停止
main停止

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值