多线程编程之join方法的详解

简介: 在Java线程编程中, join()方法主要是让调用该方法的thread在完成run方法里面的部分后, 再执行join()方法后面的代码。

1 前言


在Java线程编程中, join()方法主要是让调用该方法的thread在完成run方法里面的部分后, 再执行join()方法后面的代码。

比如:


public class Demo08 {
    public static void main(String[] args) throws InterruptedException {
        Thread08 thread08 = new Thread08();
        thread08.start();
//        thread08.join();
        System.out.println("子线程执行完成以后再执行");
    }
}
class Thread08 extends Thread{
    @Override
    public void run() {
        try {
            int value = (int) (Math.random() * 10000);
            System.out.println("需要等待" + value + "毫秒");
            Thread.sleep(value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码


不使用join方法的结果如下:


1615f186cceb43d881c9e8ec65a702d0~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


 使用了join方法之后的运行结果如下:


cdab25028e684f5daecf04fdabff9af5~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


输出第一句后等待9497毫秒,输出第二句。


join方法的作用就是使所属的线程对象A(子线程)执行run方法中的任务,而使得当前线程B(主线程)进行无限期的阻塞,等待线程A销毁后再继续 执行线程B后面的代码。方法join具有线程排队运行的作用,有些类似于同步代码运行的效果。


2 join的源码分析


从前面的例子可以发现,join具有使得线程排队的效果,就像同步一样。


但是join方法底层使用的是wait方法。


join的源码如下:


b1816a75ad164166939b5dcc27065b0d~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


可以发现这里调用了join(long millis)方法,进入join(long millis)方法如下:


948a757874014af4848e9cc045b48747~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


通过上面的方法可以发现当join(long millis)方法的参数是0时,运行这个方法的线程就会执行wait(0)。而wait方法的源码如下:


61708d16ec3843f68eaa38b148ad0c23~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


可以发现其实wait()方法其实执行的就是wait(0)。所以主线程一直等待。


而通过join(long milis)的源码可以发现,这个方法使用synchronized关键字进行修饰的,也就是调用这个方法需要获得子线程对象的锁,然后调用wait,即先获得锁后释放锁,然后等待唤醒。


当线程运行结束的时候,notify是被线程的子系统调用的,进而主线程被唤醒!


join与synchroinzed区别:join的内部使用wait方法进行等待,而synchronized关键字使用的是对象锁的机制作为同步。


3 join方法的使用


join(long)方法使用:方法join(long)中的参数是设定等待的时间。比如:


public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread10();
        t.start();
        t.join(2000);
        System.out.println("主线程结束于" + System.currentTimeMillis());
    }
}
class Thread10 extends Thread{
    @Override
    public void run() {
        try {
            System.out.println("子线程开始于" + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("子线程结束于" + System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
复制代码


结果如下:


0bea3ed9b96f4dcba1770a85fb691ebe~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


上面代码如果改成使用sleep(2000),运行的效果还是等待2000毫秒,和使用join(2000)运行效果没一样,但是它们在同步的处理上不一样,前面源码分析提到join底层是使用wait来实现所以会释放锁,而sleep不释放锁!所以joing(long)方法也具有释放同步锁的特点


比如:


public class Demo11 {
    public static void main(String[] args) {
        Thread11_A thread11_a = new Thread11_A();
        Thread11_B thread11_b = new Thread11_B(thread11_a);
        thread11_b.start();
        Thread11_C thread11_c = new Thread11_C(thread11_a);
        thread11_c.start();
    }
}
class Thread11_A extends Thread{
    @Override
    public void run() {
        try{
            System.out.println("线程A开始于:"+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("线程A结束于:"+System.currentTimeMillis());
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
    synchronized public void foo(){
        System.out.println("方法执行时间" + System.currentTimeMillis());
    }
}
class Thread11_B extends Thread {
    private Thread11_A thread11_a;
    public Thread11_B(Thread11_A thread11_a) {
        this.thread11_a = thread11_a;
    }
    @Override
    public void run() {
        synchronized (thread11_a) {
            try {
                thread11_a.start();
//                Thread.sleep(6000);
                thread11_a.join();
                for (int i = 0; i < Integer.MAX_VALUE; i++) {
                    String s = new String();
                    Math.random();
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
class Thread11_C extends Thread {
    private Thread11_A thread11_a;
    public Thread11_C(Thread11_A thread11_a) {
        this.thread11_a = thread11_a;
    }
    @Override
    public void run() {
        thread11_a.foo();
    }
}
复制代码


结果如下:


cbf0aea5ef5b4702953a5b3c66bd9a73~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


如果当join与interrupte方法如果彼此遇到,则会出现异常,但进程并没有结束,因为ThreadA还在继续运行,线程A并没有出现 异常,是正常状态下继续 执行。比如:


public class Demo09 {
    public static void main(String[] args) throws InterruptedException {
        Thread09_B t = new Thread09_B();
        t.start();
        Thread.sleep(10);
        Thread t2 = new Thread09_C(t);
        t2.start();
    }
}
class Thread09_A extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            // 耗时操作
            String s = new String();
            Math.random();
        }
    }
}
class Thread09_B extends Thread{
    @Override
    public void run() {
        try {
            Thread09_A t1 = new Thread09_A();
            t1.start();
            t1.join();
            System.out.println("B线程正常结束");
        } catch (InterruptedException e) {
            System.out.println("B线程异常结束");
            e.printStackTrace();
        }
    }
}
class Thread09_C extends Thread{
    private Thread09_B t;
    public Thread09_C(Thread09_B t){
        this.t = t;
    }
    @Override
    public void run() {
        t.interrupt();
    }
}
复制代码


结果如下:


3967f04069bd4a0cad88f36befcbbb83~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg

目录
相关文章
|
15天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
98 2
|
1月前
|
缓存 Java 调度
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
48 10
|
1月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
1月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
51 3
|
1月前
|
算法 调度 开发者
多线程编程核心:上下文切换深度解析
在多线程编程中,上下文切换是一个至关重要的概念,它直接影响到程序的性能和响应速度。本文将深入探讨上下文切换的含义、原因、影响以及如何优化,帮助你在工作和学习中更好地理解和应用多线程技术。
43 4
|
13天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
13天前
|
Java API 调度
【JavaEE】——多线程(join阻塞,计算,引用,状态)
【JavaEE】——多线程,join,sleep引起的线程阻塞,多线程提升计算效率,如何获取线程的引用和状态
|
13天前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
13天前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
15天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
44 1