目录

方法 join() 的使用

1 学习方法 join() 前的铺垫 

2 用方法 join() 解决问题

3 方法join() 和 interrupt() 出现异常

4 方法join(long)的使用 

5 方法join(long) 与 sleep(long)的区别

6 方法join()后的代码提前运行 

7 方法 join(long millis, int nanos) 的使用 


方法 join() 的使用

在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束,这时如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,这个时候就要用到 join() 方法了。方法 join() 的作用是等待线程对象销毁。 

1 学习方法 join() 前的铺垫 

在进入 join() 方法学习之前,先看一个实验。

测试代码如下:

public class joinTest {
    static class MyThread extends Thread{
        @Override
        public void run() {
            try{
                int secondValue = (int) (Math.random()*10000);
                System.out.println(secondValue);
                Thread.sleep(secondValue);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        //Thread.sleep(?)
        System.out.println("main 线程想实现当 thread 对象执行完毕后再继续向下执行 ,");
        System.out.println("但上面代码中的 sleep() 中的值应该写多少呢?");
        System.out.println("答案是:根据不能确定 :)");
    }
}

程序运行结果如图所示:

2 用方法 join() 解决问题

方法 join() 可以解决这个问题。新的测试代码如下:

public class joinTest2 {
    public static class MyThread extends Thread {
        @Override
        public void run() {
            try {
                int secondValue = (int) (Math.random() * 10000);
                System.out.println(secondValue);
                Thread.sleep(secondValue);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        try{
            MyThread thread = new MyThread();
            thread.start();
            thread.join();
            System.out.println("等待thread对象执行后再执行.....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

方法join()的作用是使所属的线程对象X正常的执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程X销毁后再继续执行线程z后面的代码。也就是说,join()方法具有串联执行的作用:你不销毁,我不继续往下走!

结果如图:

方法join()也支持多个线程的等待。

public class joinTest3 {
    static int number1 = 0;
    static int number2 = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                try{
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                number1 = 1000;
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                try{
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        long beginTime = System.currentTimeMillis();
        t1.start();
        t2.start();
        System.out.println("A "+System.currentTimeMillis());
        t1.join();
        System.out.println("B "+System.currentTimeMillis());
        t2.join();
        System.out.println("C "+System.currentTimeMillis());
        long endTime = System.currentTimeMillis();
        System.out.println("number1值为:"+number1+",number2值为:"+number2+",耗时:"+(endTime-beginTime));
    }
}

运行结果如下:

 

如果改变代码 顺序为 :

t2.join();

t1.join();

程序运行结果如下:

方法join()具有使线程排队运行的效果,有些类似synchronized同步的运行效果,但是他们的区别在于,join()在内部使用 wait() 方法进行等待,会释放锁,而synchronized关键字一致持有锁

走millis = 0 的逻辑代码:

3 方法join() 和 interrupt() 出现异常

在join()方法运行过程中,如果当前线程对象被中断,则当前线程出现异常。

public class joinTest4 {
    static class ThreadA extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                String newString = new String();
                Math.random();
            }
        }
    }
    static class ThreadB extends  Thread{
        @Override
        public void run() {
            try{
                ThreadA threadA = new ThreadA();
                threadA.start();
                threadA.join();
                System.out.println("线程B在run end 处打印了");
            } catch (InterruptedException e) {
                System.out.println("线程B在catch处打印了");
                e.printStackTrace();
            }
        }
    }
    static class ThreadC extends  Thread{
        private ThreadB threadB;

        public ThreadC(ThreadB threadB) {
            this.threadB = threadB;
        }

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

    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();
        }
    }

}

运行结果如图:

可见,如果join()与interrupt()彼此遇到,程序会出现异常,无论它们的执行顺序是怎么样的。进程按钮还是红色,原因是ThreadA还在继续运行,它并未出现异常。 

4 方法join(long)的使用 

方法 x.join(long) 中的参数用于设定最长的等待时间,当线程小于long时间销毁或long时间到达并且 重新获得了锁时,当前程序会继续向后运行。如果没有重新获得锁,线程会一直尝试,直到获得锁为止。

public class joinLong {
    static class MyThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("run begin Timer ="+System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("run   end Timer="+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        try{
            MyThread thread = new MyThread();
            thread.start();
            System.out.println("   main begin time="+System.currentTimeMillis());
            thread.join(2000);//只等2s 小于5s
            System.out.println("    main    end time="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

程序运行结果如图:

从运行结果可以看出,run()方法执行了5s,main暂停了2s。

如果更改代码,如下所示:

public static void main(String[] args) {
        try{
            MyThread thread = new MyThread();
            thread.start();
            System.out.println("   main begin time="+System.currentTimeMillis());
            thread.join(8000);//等8s 大于5s
            System.out.println("    main    end time="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

运行结果:

从运行结果上来看,run方法执行了5s ,main方法暂停了5s。

从以上案例可以分析出,join(long)方法和sleep(long)方法具有类似的功能,就是是当前的线程暂停指定的时间。两者的区别是:join(long)暂停的时间是可变的,取决于线程是否销毁,而sleep(long )暂停的时间是不确定的。另外两者还有一个关键的区别:锁是否被释放。 

5 方法join(long) 与 sleep(long)的区别

方法join(long)是在内部使用wait(long)方法实现的,所以join(long)方法具有释放锁的特点。

方法join(long)的源代码如下,是被同步 synchronized 修饰的:

public final synchronized void join(final long millis)
    throws InterruptedException {
        if (millis > 0) {
            if (isAlive()) {
                final long startTime = System.nanoTime();
                long delay = millis;
                do {
                    wait(delay);
                } while (isAlive() && (delay = millis -
                        TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
            }
        } else if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            throw new IllegalArgumentException("timeout value is negative");
        }
    }

从源代码中可以了解到,当执行wait(long)方法后锁被释放,那么其他线程就可以调用此线程中的同步方法了。当start()后的线程销毁时会在本地使用C语言代表通知所有的等待者,等待者被唤醒后继续执行后面的代码。

而Thread. sleep(long)方法却不释放锁,我们来看看下面的示例。

public class join_sleep {
    static class ThreadA extends Thread{
        private ThreadB threadB;

        public ThreadA(ThreadB threadB) {
            this.threadB = threadB;
        }

        @Override
        public void run() {
            try{
                synchronized (threadB){
                    threadB.start();
                    Thread.sleep(6000);
                    //不释放锁
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class ThreadB extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("    b run begin timer="+System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("    b run   end timer="+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        synchronized public void bService(){
            System.out.println("打印了bService timer="+System.currentTimeMillis());
        }
    }
    static class ThreadC extends Thread{
        private ThreadB threadB;

        public ThreadC(ThreadB threadB) {
            this.threadB = threadB;
        }

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

    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();
        }
    }
}

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

下面将继续验证 join() 方法释放锁的特点。

创建实验用的项目 join_sleep_2,将join_sleep_1中的所有代码复制到join_sleep_2项目中,更改ThreadA类中的代码,如下:

static class ThreadA extends Thread{
        private ThreadB threadB;

        public ThreadA(ThreadB threadB) {
            this.threadB = threadB;
        }

        @Override
        public void run() {
            try{
                synchronized (threadB){
                    threadB.start();
                    threadB.join();//执行join()方法的一瞬间,b立即释放锁。
                    for (int i = 0; i < Integer.MAX_VALUE; i++) {
                        String newString = new String();
                        Math.random();
                    }
                    //不释放锁
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

程序运行结果如图:

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

另外还需要注意的一点,join()无参方法或join(time)有参方法一旦执行,说明源代码中的wait(time)已经被执行,也就证明锁立即被释放,仅仅在指定的 join(time) 时间后当前线程才会继续运行。 

6 方法join()后的代码提前运行 

测试前文的代码可能遇到陷阱,稍有不注意就有可能掉进坑里。 

public class joinMore {
    static class ThreadA extends Thread {
        private ThreadB threadB;

        public ThreadA(ThreadB threadB) {
            this.threadB = threadB;
        }

        @Override
        public void run() {
            try {
                synchronized (threadB) {
                    System.out.println("begin A ThreadName="
                            + Thread.currentThread().getName() + " "
                            + System.currentTimeMillis());
                    Thread.sleep(500);
                    System.out.println("    end A threadName="
                            + Thread.currentThread().getName() + " "
                            + System.currentTimeMillis());
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        synchronized public void run() {
            try {
                System.out.println("begin B ThreadName="
                        + Thread.currentThread().getName() + " "
                        + System.currentTimeMillis());
                Thread.sleep(500);
                System.out.println("    end B   ThreadName="
                        + Thread.currentThread().getName() + " "
                        + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        try {
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            b.start();
            b.join(200);
            System.out.println("        main end " + System.currentTimeMillis());
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

程序运行后,在控制台中打印结果可能如图1:

也可能图2: 

为什么会出现不同的运行结果呢?

修改main()函数:

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());
    }

结果如图:

由此可见:多数时候 main end 是第一个打印的。所以可以得出如下结论:如果将System.out.println()方法当作join(200),join(200)大部分是可以先运行的,也就是先抢到ThreadB的锁,然后快速释放。但由于线程执行run()方法的随机性,ThreadA 和 B以及main都有可能先获得锁。 

所以,图一:

join(200)先抢到b锁,释放执行内部wait(200)后立即释放, A抢到执行 打印begin sleep (500) 打印 end 后释放锁,执行完后wait(200)时间已到还剩300ms时,main和B争抢锁,main抢到后释放,B抢到,同时main和B又争抢PrintStream的锁main抢到打印后释放,B抢到打印。

同理,图二就是最后B先抢到PinrtStream的锁打印begin后释放,main抢到打印后,B再抢到打印end。

7 方法 join(long millis, int nanos) 的使用 

源代码:

public final synchronized void join(long millis, int nanos)
    throws InterruptedException {

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        join(millis);
    }

方法等待的时间最长为millis毫秒+nanos纳秒,如果参数nanos<0或>999999就会出现异常。同时,这个等待时间也并非完全精确的,是在原先的基础上+1。

测试代码:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        long begin = System.currentTimeMillis();
        Thread.currentThread().join(2000,999999);
        long endTime = System.currentTimeMillis();
        System.out.println(endTime-begin);
    }
}

程序显示方法join()耗时2001毫秒。 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐