【多线程: 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都会执行其他线程

目录
相关文章
|
8天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
8天前
|
Java API 调度
【JavaEE】——多线程(join阻塞,计算,引用,状态)
【JavaEE】——多线程,join,sleep引起的线程阻塞,多线程提升计算效率,如何获取线程的引用和状态
|
8天前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
40 3
|
3月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
28 2
|
3月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
25 1
|
3月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
49 1
|
3月前
|
Java
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅。它们用于线程间通信,使线程能够协作完成任务。通过这些方法,生产者和消费者线程可以高效地管理共享资源,确保程序的有序运行。正确使用这些方法需要遵循同步规则,避免虚假唤醒等问题。示例代码展示了如何在生产者-消费者模型中使用`wait()`和`notify()`。
37 1
|
3月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
57 1
|
3月前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
45 1