JAVA多线程基础篇-join方法的使用

本文详细介绍了Java线程的join()方法,包括其使用方式、工作原理和注意事项。join()方法使得当前线程等待指定线程执行完毕后再继续,常用于线程间的同步。此外,还探讨了join()与sleep()的区别,以及interrupt()对join()的影响。在实际开发中,合理使用join()可以有效控制线程执行顺序,避免数据竞争问题。
摘要由CSDN通过智能技术生成

1.概述

join()是Thread类中的一个方法,它的作用是将当前线程挂起,等待其他线程结束后再执行当前线程,即当前线程等待另一个调用join()方法的线程执行结束后再往下执行。通常用于在main主线程内,等待其它调用join()方法的线程执行结束再继续执行main主线程。本文将探索join方法的使用方式和使用原理。

2.join方法使用

2.1 join()示意图

2.1.1线程运行示意图

在这里插入图片描述

上述案例示意图中,主线程A入栈运行,运行时创建子线程B和线程C执行任务,B线程或C线程调用join方法,阻塞当前线程A,B或C线程执行完成,A线程才会继续执行。

2.1.2案例代码

public class ThreadB extends Thread {

    public void run() {
        System.out.println("线程:" + Thread.currentThread().getName() + "休眠:10s");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("线程:" + Thread.currentThread().getName() + "休眠结束");
    }

}

public class ThreadC extends Thread {

    public void run() {
        System.out.println("线程:" + Thread.currentThread().getName() + "休眠:1s");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("线程:" + Thread.currentThread().getName() + "休眠结束");
    }

}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程线程A开始运行。。。");
        ThreadB threadB = new ThreadB();
        threadB.setName("ThreadB");
        threadB.start();

        ThreadC threadC = new ThreadC();
        threadC.setName("ThreadC");
        threadC.start();

        threadB.join();

        System.out.println("主线程线程A结束运行");

    }
}

运行结果如下:
在这里插入图片描述
方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x被销毁后再继续执行线程z后面的代码。

2.2 join方法原理

先看一下join()方法的源码,源码基于JDK 1.8,具体如下:

 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) {
           //判断线程是否存活,若为true,调用wait()方法阻塞
            while (isAlive()) {
                wait(0);
            }
        } else {
            //阻塞指定时间
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

由上述源码可知,join方法的底层实际上还是调用了wait()方法,通过wait()方法实现线程阻塞等待,那么这里有个问题,阻塞完成后,如何通知呢?我们知道调用wait阻塞的线程,需要notify()或者notifyAll来进行唤醒,这里并没有显式调用这两个方法,那是如何实现的呢?这里涉及一个知识:

在java中,Thread类线程执行完run()方法后,会自动执行notifyAll()方法。

具体的实现在HotSpot的源码中,有个ensure_join方法,大家感兴趣可查看。

2.3 注意事项

2.3.1 join方法被interrupt会异常

测试代码如下:

public class ThreadA extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(i);
            String s = stringBuffer.toString();
        }
    }
}


public class ThreadB extends Thread {

    @SneakyThrows
    @Override
    public void run() {
        ThreadA threadA = new ThreadA();
        threadA.start();
        threadA.setName("ThreadA");
        threadA.join();
        System.out.println(Thread.currentThread().getName() + "运行完成!");
    }

}

public class ThreadC extends Thread {

    private ThreadB threadB;

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

    @Override
    public void run() {
        threadB.interrupt();
        System.out.println(Thread.currentThread().getName() + "运行结束!");
    }
}


public class Test {

    public static void main(String[] args) {
        ThreadB threadB = new ThreadB();
        threadB.start();
        threadB.setName("ThreadB");

        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception e) {
            e.printStackTrace();
        }
        ThreadC threadC = new ThreadC(threadB);
        threadC.setName("ThreadC");
        threadC.start();
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }
}

运行结果如下:
在这里插入图片描述
上述代码中,ThreadB 中创建了一个ThreadA执行,并让threadA.join(),又让线程ThreadC调用线程ThreadB的interrupt()方法来打断进程。由运行结果可知,方法join()与interrupt()相遇,会出现异常,而上述进程又未终止,说明ThreadA还在运行。

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

由上述join方法的源码可知,在执行完wait(long)方法后,当前线程的锁被释放,那么其它线程就可以调用此线程中的同步方法。sleep(long)方法却不释放锁,也就意味着当线程调用sleep方法时,线程会被阻塞,其它线程只能等待获取锁。
测试代码如下:

public 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(6000L);
                System.out.println(Thread.currentThread().getName() + "结束运行!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


public class ThreadB extends Thread {

    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始运行:" + System.currentTimeMillis());
        try {
            Thread.sleep(5000L);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束:" + System.currentTimeMillis());
    }

    public synchronized void print() {
        System.out.println(Thread.currentThread().getName() + "打印了时间:" + System.currentTimeMillis());
    }
}

public class ThreadC extends Thread {

    private ThreadB threadB;

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

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始运行!");
        threadB.print();
        System.out.println(Thread.currentThread().getName() + "运行完成!");
    }
}


public class Test {
    public static void main(String[] args) {
        ThreadB threadB = new ThreadB();
        threadB.setName("ThreadB");
        ThreadA threadA = new ThreadA(threadB);

        threadA.start();
        threadA.setName("ThreadA");
        try {
            Thread.sleep(1000L);
            ThreadC threadC = new ThreadC(threadB);
            threadC.setName("ThreadC");
            threadC.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:
在这里插入图片描述
由上述代码可知,线程ThreadA调用sleep方法一直持有线程ThreadB对象的锁,时间达到6s,所以线程ThreadC只能在ThreadA时间达到6s后释放线程ThreadB的锁时,才可以调用ThreaB的同步方法print()。由上述现象可知,Thread.sleep(long)不释放锁。
修改上述ThreadA中的代码,验证join()方法释放锁,具体代码如下:

public 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(6000L);
                threadB.join();
                System.out.println(Thread.currentThread().getName() + "结束运行!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:
在这里插入图片描述
由上述运行结果可知,在线程ThreadA中调用ThreadB的join()方法,线程ThreadC在ThreadB未执行完成后,获取到了锁,执行了同步方法print(),说明join(long)方法会释放锁。

2.3.3 join()方法后的代码提前执行

案例代码如下:

public class ThreadA extends Thread {

    private ThreadB threadB;

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

    @Override
    public void run() {
        try {
            synchronized (threadB) {
                System.out.println(Thread.currentThread().getName() + "开始运行!");
                Thread.sleep(5000L);
                System.out.println(Thread.currentThread().getName() + "结束运行!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class ThreadB extends Thread {

    public synchronized void run() {
        System.out.println(Thread.currentThread().getName() + "开始运行:" + System.currentTimeMillis());
        try {
            Thread.sleep(5000L);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束:" + System.currentTimeMillis());
    }
}


public static void main(String[] args) {
        try {
            ThreadB threadB = new ThreadB();
            ThreadA threadAA = new ThreadA(threadB);
            threadAA.start();
            threadAA.setName("threadAA");
            threadB.setName("threadB");
            threadB.start();
            threadB.join(2000);
            System.out.println(Thread.currentThread().getName() + "结束运行!");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

运行结果如下:
在这里插入图片描述
出现上述情况的原因如下:

1.b.join(2000)方法先抢到threadB锁,然后将锁释放;
2.ThreadA抢到锁,打印:threadAA开始运行,并且sleep 5s;
3.ThreadA打印:threadAA运行结束,并释放锁;
4.此时join(2000)和ThreadB争抢锁,而join(2000)再次抢到锁,发现时间已过,释放锁后打印:main运行结束;
5.ThreadB抢到锁打印ThreadB开始运行;
6.5s后ThreadB打印:threadB运行结束。

2.3.4 Thread.currentThread().join()方法

若调用这个方法,当前线程将一直被阻塞,无法退出,开发中因谨慎使用。修改2.1.2节中测试代码为:

public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程线程A开始运行。。。");
        ThreadB threadB = new ThreadB();
        threadB.setName("ThreadB");
        threadB.start();

        ThreadC threadC = new ThreadC();
        threadC.setName("ThreadC");
        threadC.start();

        //threadB.join();
        Thread.currentThread().join();

        System.out.println("主线程线程A结束运行");
}

出现运行结果如下:
在这里插入图片描述

3.小结

1.join()方法主要使用场景是一个线程需要等待另线程的运行结果,需要阻塞当前线程等待;
2.join()和join(long)的主要区别是:join(long)阻塞指定时间;join()一直阻塞,直至线程被销毁;
3.join() 方法被interrupt()会抛出异常,join()方法使用后会释放锁,sleep(long)方法却不释放锁;
4.Thread.currentThread().join()方法会一直阻塞线程。

4.参考文献

1.https://www.jb51.net/article/216689.htm
2.《JAVA多线程编程核心技术》-高洪岩著

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值