【多线程: join 方法详解】

简介: 【多线程: join 方法详解】

【多线程: join 方法详解】

01.为什么需要 join

join的理解:

join的目的是为了把调用join的线程“插队”到了当前线程,并且 调用join的线程一定会把此线程运行结束。

补充两个概念

同步:需要等待结果返回,才能继续运行就是同步

异步:不需要等待结果返回,就能继续运行就是异步

02.join的使用

分析这段代码 说明r的值

import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test10")
public class Test10 {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        log.debug("结果为:{}", r);
        log.debug("结束");
    }
}

结果

14:50:09.053 c.Test10 [main] - 开始
14:50:09.096 c.Test10 [t1] - 开始
14:50:09.095 c.Test10 [main] - 结果为:0
14:50:09.097 c.Test10 [main] - 结束
14:50:09.098 c.Test10 [t1] - 结束

解释

因为此时为异步,且t1线程在给r赋值前 sleep了1秒,导致主线程先运行了==log.debug("结果为:{}", r)==这个语句 此时这个r还没有被赋值故结果为0

给t1线程加入join后

import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test10")
public class Test10 {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        t1.join();
        log.debug("结果为:{}", r);
        log.debug("结束");
    }
}

结果

14:52:21.241 c.Test10 [main] - 开始
14:52:21.277 c.Test10 [t1] - 开始
14:52:21.279 c.Test10 [t1] - 结束
14:52:21.279 c.Test10 [main] - 结果为:10
14:52:21.280 c.Test10 [main] - 结束

解释

因为此时是同步,t1.join()后,把t1线程加入到了主线程,并且把t1执行完成后才会继续执行主线程,所以此时r已经赋值,所以此时r=10

03.一个例子

分析下面这段代码花费的时间

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.TestJoin")
public class TestJoin {
    static int r = 0;
    static int r1 = 0;
    static int r2 = 0;

    public static void main(String[] args) throws InterruptedException {
        test();
    }

    private static void test() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r2 = 20;
    
        });
        t1.start();
        t2.start();
        long start = System.currentTimeMillis();
        log.debug("join begin");
        t1.join();
        log.debug("t1 join end");
        t2.join();
        log.debug("t2 join end");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
}

结果

15:23:18.025 c.TestJoin [main] - join begin
15:23:19.025 c.TestJoin [main] - t1 join end
15:23:20.025 c.TestJoin [main] - t2 join end
15:23:20.025 c.TestJoin [main] - r1: 10 r2: 20 cost: 2002

解释

因为t1线程先同步到主线程 但是t1线程sleep了1s 在这个过程中 程序在t2线程运行1s,之后继续运行t1线程,t1线程结束后 程序运行t2线程 t2线程同步到主线程 因为t2线程之前已经运行过1s 又因为t2线程sleep 2s 所以还需要1s,故总共需要时间为2s

若把t1 t2互换结果会有改变吗?

分析

结果是不会变的 还是2s ,因为 t2线程先同步到了主线程 t2线程sleep了2s 这期间 运行了t1线程 因为t1线程只是sleep了1s 故 t1线程运行结束 当2s后 切换到t1线程 t1线程运行结束,故总共还是2s

画图分析

t1在t2前的情况

202206262003923.png

202206262003297.png

202206262003323.png

202206262003352.png

202206262003384.png

202206262003412.png

再思考一个问题

因为我的电脑是多核cpu的,结果是2s,那么单核cpu结果还一样吗?

提出这个问题的原因是 会有人认为之所以 在t1线程睡眠期间 可以运行t2线程是因为另一个cpu运行了t2线程,那么按照这种思想 单核cpu在t1睡眠期间 是不会运行t2线程的,这样最后的时间就是3s

在单核服务器上查看运行结果

可以看到结果依旧是2s 说明上述推断是错误的,无论是单核还是多核 sleep过程中 空闲cpu都会执行其他线程

目录
相关文章
|
3月前
|
Java 调度
Java并发基础-线程简介(状态、常用方法)
Java并发基础-线程简介(状态、常用方法)
32 0
|
16天前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
23天前
|
算法 安全 Java
三种方法教你实现多线程交替打印ABC,干货满满!
本文介绍了多线程编程中的经典问题——多线程交替打印ABC。通过三种方法实现:使用`wait()`和`notify()`、`ReentrantLock`与`Condition`、以及`Semaphore`。每种方法详细讲解了实现步骤和代码示例,帮助读者理解和掌握线程间的同步与互斥,有效解决并发问题。适合不同层次的开发者学习参考。
42 11
|
17天前
|
Java Spring
运行@Async注解的方法的线程池
自定义@Async注解线程池
41 3
|
28天前
|
安全 Java API
|
1月前
|
Java
java开启线程的四种方法
这篇文章介绍了Java中开启线程的四种方法,包括继承Thread类、实现Runnable接口、实现Callable接口和创建线程池,每种方法都提供了代码实现和测试结果。
java开启线程的四种方法
【多线程面试题 二】、 说说Thread类的常用方法
Thread类的常用方法包括构造方法(如Thread()、Thread(Runnable target)等)、静态方法(如currentThread()、sleep(long millis)、yield()等)和实例方法(如getId()、getName()、interrupt()、join()等),用于线程的创建、控制和管理。
|
1月前
|
Dart API C语言
Dart ffi 使用问题之想在C/C++中创建异步线程来调用Dart方法,如何操作
Dart ffi 使用问题之想在C/C++中创建异步线程来调用Dart方法,如何操作
|
1月前
|
Java UED
基于SpringBoot自定义线程池实现多线程执行方法,以及多线程之间的协调和同步
这篇文章介绍了在SpringBoot项目中如何自定义线程池来实现多线程执行方法,并探讨了多线程之间的协调和同步问题,提供了相关的示例代码。
177 0
|
3月前
|
Java 开发者
线程通信的方法和实现技巧详解
线程通信的方法和实现技巧详解