在很多情况下,主线程穿件并启动子线程,如果子线程中药进行大量的耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join方法了。方法join的作用是等待线程对象销毁。
先来看一下一个示例代码:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class MyThread extends Thread{
    @Override
    public void run() {
        try{
            int secondValue = (int)(Math.random()*1000);
            System.out.println(secondValue);
            Thread.sleep(secondValue);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
package JoinTest;

/**
 * @Author LiBinquan
 */
public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("我想当threadTest对象执行完毕后我再执行");
        System.out.println("但上面代码中的sleep 中的值应该写多少呢");
        System.out.println("答案是:不能确定");
    }
}

输出:

java的线程join方法的作用_ide

1.使用join方法解决

修改Test.java

package JoinTest;

/**
 * @Author LiBinquan
 */
public class Test {
    public static void main(String[] args) {
        try {
            MyThread myThread = new MyThread();
            myThread.start();
            myThread.join();
            System.out.println("我想当threadTest对象执行完毕后我再执行");
            System.out.println("但上面代码中的sleep 中的值应该写多少呢");
            System.out.println("答案是:不能确定");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

输出:

java的线程join方法的作用_多线程_02


方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。

方法join具有使线程排队运行的作用,有些类似同步的运行效果。join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized关键字使用的是”对象监视器“原理做为同步。

2.方法join与异常

在join过程中,如果当前线程对象被中断,则当前线程出现异常。
示例代码:
线程a:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class ThreadA extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            String str = new String();
            Math.random();
        }
    }
}

线程b:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class ThreadB extends Thread{
    @Override
    public void run() {
        try{
            ThreadA a = new ThreadA();
            a.start();
            a.join();
            System.out.println("线程B在run end 处打印");
        }catch (InterruptedException e){
            System.out.println("线程B在异常处打印");
            e.printStackTrace();
        }

    }
}

线程c:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class ThreadC extends Thread{
    private ThreadB threadB;
    public ThreadC(ThreadB threadB){
        this.threadB = threadB;
    }

    @Override
    public void run() {
        threadB.interrupt();
    }
}

运行类:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) {
        try{
            ThreadB b = new ThreadB();
            b.start();
            Thread.sleep(500);
            ThreadC c = new ThreadC(b);
            c.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

输出:

java的线程join方法的作用_System_03


由输出说明,方法join和interrupt方法如果遇到,则会出现异常,但是进程的按钮还是呈”红色“,原因是线程ThreadA并未出现异常时正常执行的状态。

3.方法join(long)的使用

方法join(long)中的参数是设定等待的时间。
示例代码:
Mythread类:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class MyThread extends Thread{
    @Override
    public void run() {
        try{
            System.out.println("begin Time = "+System.currentTimeMillis());
            Thread.sleep(5000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行类:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class Test {
    public static void main(String[] args) {
        try {
            MyThread myThread = new MyThread();
            myThread.start();
            myThread.join(2000);
            System.out.println("end time = "+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

输出:

java的线程join方法的作用_System_04


其实这边将myThread.join(2000)改为myThread.sleep(2000)运行结果还是等待了2秒。有兴趣可以自己修改运行一下。

4.方法join(long)与sleep(long)的区别

方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。
join的源码如下:

/**
     * 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);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

从源码中可以了解到,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。
而Thread.sleep(long)方法却不释放锁。在下面的示例中将测试Thread.sleep(long)方法具有不释放锁的特点。
示例代码如下:
线程A

package JoinTest;

/**
 * @Author LiBinquan
 */
public class ThreadA extends Thread{
    private ThreadB threadB ;
    public ThreadA(ThreadB threadB){
        super();
        this.threadB = threadB;
    }
    @Override
    public void run() {
        try{
            synchronized (threadB){
                threadB.start();
                Thread.sleep(6000);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

线程b

package JoinTest;

/**
 * @Author LiBinquan
 */
public class ThreadB extends Thread{
    @Override
    public void run() {
        try{
            System.out.println("  b run begin time = "+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("  b run   end time = "+System.currentTimeMillis());
        }catch (InterruptedException e){
            System.out.println("线程B在异常处打印");
            e.printStackTrace();
        }
    }
    synchronized public void bService(){
        System.out.println("打印了bService time = "+System.currentTimeMillis());
    }
}

线程c

package JoinTest;

/**
 * @Author LiBinquan
 */
public class ThreadC extends Thread{
    private ThreadB threadB;
    public ThreadC(ThreadB threadB){
        this.threadB = threadB;
    }

    @Override
    public void run() {
        threadB.bService();
    }
}

运行类:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) {
        try{
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            Thread.sleep(1000);
            ThreadC c = new ThreadC(b);
            c.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

输出:

java的线程join方法的作用_多线程_05


由输出可得,由于线程ThreadA使用Thread.sleep(long)方法一直持有ThreadB对象的锁,时间达到6秒,所以线程ThreadC只有在ThreadA时间达到6秒后释放ThreadB的锁时,才可以调用ThreadB中的同步方法synchronized public void bService().

上面测试证明Thread.sleep(long)方法不释放锁。

接下来测试join()方法释放锁。

修改线程A

package JoinTest;

/**
 * @Author LiBinquan
 */
public class ThreadA extends Thread{
    private ThreadB threadB ;
    public ThreadA(ThreadB threadB){
        super();
        this.threadB = threadB;
    }
    @Override
    public void run() {
        try{
            synchronized (threadB){
                threadB.start();
                threadB.join();
                for (int i = 0; i < Integer.MAX_VALUE; i++) {
                    String str = new String();
                    Math.random();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

输出:

java的线程join方法的作用_java的线程join方法的作用_06


由于线程ThreadA释放了ThreadB的锁,所以线程ThreadC可以调用ThreadB中的同步方法synchronized public void bService();

5.方法join()后面的代码提前运行:出现意外

这边说明一下使用join会遇到的陷阱。
线程A:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class ThreadA extends Thread{
    private ThreadB threadB ;
    public ThreadA(ThreadB threadB){
        super();
        this.threadB = threadB;
    }
    @Override
    public void run() {
        try{
            synchronized (threadB){
                System.out.println("begin A ThreadName = "+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("  end A ThreadName = "+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

线程B

package JoinTest;

/**
 * @Author LiBinquan
 */
public class ThreadB extends Thread{
    @Override
    synchronized public void run() {
        try{
                System.out.println("begin B ThreadName = "+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("  end B ThreadName = "+Thread.currentThread().getName()+"  "+System.currentTimeMillis());

        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    synchronized public void bService(){
        System.out.println("打印了bService time = "+System.currentTimeMillis());
    }
}

运行类:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) {
        try{
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            b.start();
            b.join(2000);
            System.out.println("       main end "+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

输出:

java的线程join方法的作用_多线程_07


java的线程join方法的作用_多线程_08


这个就是陷阱,就是join方法后面的代码提前运行了。

6.方法join()后面的代码提前运行:释放意外

示例代码:
修改run运行类:

package JoinTest;

/**
 * @Author LiBinquan
 */
public class Run {
    public static void main(String[] args) {
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            b.start();
            System.out.println("       main end "+System.currentTimeMillis());
    }
}

输出:

java的线程join方法的作用_ide_09


我们可以多次运行,然后可以发现:main end 往往都会第一个打印。所以可以完全确定得出:方法join(2000)大部分是先运行的,也就是先抢到ThreadB的锁,然后快速进行释放。