线程间通信-方法join的使用

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

学习join方法前的铺垫:

创建如下代码:

public class MyThread extends Thread{
    @Override
    public void run() {
        int sencondValue = (int)(Math.random()*1000);
        System.out.println(sencondValue);
        try {
            Thread.sleep(sencondValue);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        //Thread.sleep(?);
        System.out.println("当myThread对象执行完毕后再执行");
        System.out.println("但上面代码的sleep的值写多少");
        System.out.println("答案是不确定");
    }
}

程序运行结果如下:
这里写图片描述

用join()方法来解决:

方法join可以解决这个问题。创建如下代码:

public class MyThread extends Thread{
    @Override
    public void run() {
        int sencondValue = (int)(Math.random()*1000);
        System.out.println(sencondValue);
        try {
            Thread.sleep(sencondValue);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        myThread.join();
        System.out.println("当对象myThread执行完毕后再执行");
    }
}

执行结果如下:
这里写图片描述
  方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期阻塞,等待线程x销毁后再继续执行线程z后面的代码。
方法join具有使线程排队运行的作用,有些类似同步的效果。join与synchronized的区别是:join内部是使用wait()方法进行等待的,而synchronized关键字是使用的是“对象监视器”原理做为同步。

方法join与异常:

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

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

            e.printStackTrace();
        }
    }
}
public class ThreadC extends Thread{
    private ThreadB threadb;
    public ThreadC(ThreadB threadb){
        this.threadb = threadb;
    }

    @Override
    public void run() {
        threadb.interrupt();
    }
}
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(Exception e){
            e.printStackTrace();
        }
    }
}

执行结果如下:
这里写图片描述
说明方法join()与interrupt()方法如果彼此相遇,则会出现异常。但进程按钮还是呈红色状态,原因是线程ThreadA还在继续运行,线程ThreadA并未出现异常,是正常的状态。

方法join(long)的使用:

方法join(long)中的参数是设定等待的时间。创建如下代码:

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("begin Time="+System.currentTimeMillis());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        myThread.join(2000);
        System.out.println("  end Time="+System.currentTimeMillis());
    }
}

执行结果如下:
这里写图片描述
运行结果等待了2秒。
但将main方法中的代码改为使用sleep(2000)方法时,运行的效果还是等待了2秒。如下所示:

public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        // myThread.join(2000);
        Thread.sleep(2000);
        System.out.println("  end Time=" + System.currentTimeMillis());
    }
}

这里写图片描述

  那使用sleep(2000)和使用join(2000)有什么区别,从上面的例子运行效果是看不出区别的,其实主要区别还是来自于这2个方法对同步的处理上。

方法join(long)和sleep(long)的区别:

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

 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)方法却不释放锁。创建如下代码:

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

    synchronized public void bService(){
        System.out.println("打印了 bService timer="+System.currentTimeMillis());
    }
}
public class ThreadA extends Thread{
    private ThreadB threadB;

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

    @Override
    public void run() {
        synchronized(threadB){
            threadB.start();
            try {
                Thread.sleep(6000);
                //Thread.sleep()方法不释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadC extends Thread{
    private ThreadB threadB;

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

    @Override
    public void run() {
        threadB.bService();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        ThreadB threadB = new ThreadB();
        ThreadA threadA = new ThreadA(threadB);
        threadA.start();
        Thread.sleep(2000);
        ThreadC threadC = new ThreadC(threadB);
        threadC.start();
    }
}

执行结果如下:
这里写图片描述
由于线程threadA使用了Thread.sleep(long)方法一直持有ThreadB的对象锁,时间达到6秒,所以线程ThreadC只有在ThreadA时间达到6秒后释放ThreadB的锁时,才可以调用ThreadB中的同步方法synchronized public void bService()。
此实验也说明Thread.sleep(long)方法不释放锁。
修改ThreadA类如下:

public class ThreadA extends Thread{
    private ThreadB threadB;

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

    @Override
    public void run() {
        synchronized(threadB){
            threadB.start();
            try {
                //Thread.sleep(6000);
                //Thread.sleep()方法不释放锁
                threadB.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果如下:
这里写图片描述
由于线程ThreadA释放了ThreadB的锁,所以线程ThreadC可以调用线程ThreadB的同步方法synchronized public void bService()。
这个实验也说明join(long)方法具有释放锁的功能。

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

如果不注意使用join(long)方法的使用,就会掉进“陷进”里。创建如下代码:

public class ThreadA extends Thread{
    private ThreadB threadB;
    public ThreadA(ThreadB threadB){
        this.threadB = threadB;
    }

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

    @Override
    synchronized public void run() {
        System.out.println("begin B ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end   B ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        ThreadB threadB = new ThreadB();
        ThreadA threadA = new ThreadA(threadB);
        threadA.start();
        threadB.start();
        threadB.join(2000);
        System.out.println("main end "+System.currentTimeMillis());
    }
}

执行结果如下:
这里写图片描述

这里写图片描述
为什么会出现不同的结果呢?
为了查看join方法在Run类中执行的时机,创建如下代码:

public class RunFirst {
    public static void main(String[] args) {
        ThreadB threadB = new ThreadB();
        ThreadA threadA = new ThreadA(threadB);
        threadA.start();
        threadB.start();
        System.out.println("                   main end "+System.currentTimeMillis());
    }
}

执行结果如下:
第一次运行结果:
这里写图片描述
第二次运行结果:
这里写图片描述
通过多次运行RunFirst类后,可以发现一个规律:main end往往都是第一个打印的。所以可以完全确定地得出一个结论:方法join(2000)大部分是先执行的,也就是先抢到ThreadB的锁,然后快速释放。
而执行Run类就会出现一些不同的结果。先看下面:
这里写图片描述
1、b.join(2000)方法先抢到B的锁,然后将B锁进行释放。
2、ThreadA抢到锁,打印ThreadA begin并且Thread.sleep(5000)。
3、ThreadA打印ThreadA end。
4、这时join(2000)和ThreadB争抢锁,而join(2000)再次获得锁,发现时间已过,释放锁后打印main end。
5、ThreadB获得锁,并且Thread.sleep(5000)。
6、5秒后打印ThreadB end。
再看下图
这里写图片描述
1、 join(2000)方法抢到B锁,然后马上释放。
2、 ThreadB抢到锁,打印ThreadB begin并且Thread.sleep(5000).
3、 5秒后打印ThreadB end,释放锁。
4、 这时join(2000)和ThreadA争抢锁,ThreadA获得锁,打印ThreadA begin并且Thread.sleep(5000)。
5、 5秒后打印ThreadA end。
6、 最后打印main end。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值