6.Java多线程详解:Thread类、线程属性与start()方法深度解析

目录

[Thread 类其他的属性和方法~~](#Thread 类其他的属性和方法~~)

[Thread 构造方法概览:](#Thread 构造方法概览:)

[name 属性说明:](#name 属性说明:)

[Thread 的几个常见属性](#Thread 的几个常见属性)

[前台线程 vs 后台线程](#前台线程 vs 后台线程)

[启动一个线程 - start()](#启动一个线程 - start())

中断一个线程

[启动一个线程 - start()](#启动一个线程 - start())

[变量捕获与 lambda / 匿名内部类](#变量捕获与 lambda / 匿名内部类)

[Thread 提供更靠谱的方案实现上述的效果](#Thread 提供更靠谱的方案实现上述的效果)

[日常开发中 catch 中的逻辑一般不会这么写~~](#日常开发中 catch 中的逻辑一般不会这么写~~)


Thread 类其他的属性和方法~~

Thread 构造方法概览:

  • Thread()

    创建线程对象。

  • Thread(Runnable target)

    使用 Runnable 对象创建线程对象。

  • Thread(String name)

    创建线程对象,并命名。

  • Thread(Runnable target, String name)

    使用 Runnable 对象创建线程对象,并命名。

  • 【了解】Thread(ThreadGroup group, Runnable target)

    使用 Runnable 对象创建线程对象,并分组。

    说明:线程组可以方便批量管理,分组的组别为线程组。多用在底层代码开发中。

批注name给线程起名字------不影响线程的执行,方便调试~~


name 属性说明:

默认线程名字为 Thread-数字(如 Thread-0, Thread-1等)。

代码示例

java 复制代码
Thread t3 = new Thread(() -> {
    while (true) {
        System.out.println("t3");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}, "t3");

Thread 的几个常见属性

属性 获取方法 说明
ID getId() ID 是线程的唯一标识,不同线程不会重复
名称 getName() 全是给程序员调试工具用
状态 getState() 状态表示线程所处的某个情况,下面我们会进一步说明
优先级 getPriority() 优先级高的线程理论上更容易被调度到
是否后台线程 isDaemon() 关于后台线程,再说后一点。JVM 会在所有程序的所有后台线程结束后,才会结束运行。是否后台,但简单的理解,为 true 方法是否运行起来
是否存活 isAlive() 线程的中断问题,下面我们进一步说明

补充说明

  • 线程组(ThreadGroup)现在很少涉及到,后续介绍"线程池"生态位上替代了线程组~~

  • main线程包括代码中手动创建的线程,默认都是前台线程~~

  • 返回 0 表示进程执行成功,非 0 值表示失败,使用不同的值表示不同的错误原因~~

  • 学习 C 语言的时候,hello world中的 int main() { return 0; },进程结束返回码~~


前台线程 vs 后台线程

  • 前台线程:会阻止整个进程结束。

    举例:李局长要是撤了,宴席就会结束了(主角退场)。

  • 后台线程:不会阻止整个进程结束。

    举例:如果我吃饱了,我说我要撤了,是否会使宴席结束呢?------后台线程~~

一个进程中的前台线程,不止有一个~~

得是所有的前台线程都结束了,进程才结束~~
后台线程:也叫做"守护线程"

"默默"背后默默的守护~~

"等你下课" 🎵


启动一个线程 - start()

start操作本质上会调用操作系统提供的 API,在操作系统的内部(内核)创建出一个线程出来了。

java 复制代码
private native void start0();
  • Java 的代码 => .class,再经过 JVM 解释执行的~~

  • 带有 native字样的方法,就是在 JVM 内部通过 C++ 实现的方法。

  • 调用操作系统的原生 API 创建线程,根据当前的操作系统做区分~~

  • 内部是一系列的 #if#ifdef#else if#elseC/C++来说)

  • 操作系统内部,通过 PCB​ 来描述,通过链表组织~~

  • 对于 Linux 来说(Linux 开源,Windows 闭源)

    "被调度起来后执行的逻辑,就是 run 里面设定的刚刚逻辑~~"

图示说明

  • 操作系统拿着 PCB 结构体来进行调度执行~~

  • pid:每个 PCB 对应一个线程,在 PCB 上有一个特质的 id属性,这个 id是相同同的,也就是一个进程~~

start 方法本身,执行速度非常快~~

start针对一个 thread 对象,只能调用一次~~

Java 设定了,要让一个 thread 对象和一个操作系统的线程一一对应~~


中断一个线程

英文术语:Interrupt------ 我个人更喜欢称为"终止"

对于 Java 来说,一个线程终止,就是这个线程的入口方法执行完毕~~

Java 并不提供"强制终止"(所有的让线程终止的做法,都需要等待着"让入口方法结束")

有些危险

  • A 线程调用方法,终止 B 线程。

  • 调用 A 的终止方法时,无法确定 B 线程当前执行到哪个环节了~~

  • 做某个事情,做了一半,就被强制终止了~~


启动一个线程 - start()

之前我们已经看到了如何通过重写 run方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。

  • 重写 run方法是提供给线程要做的事情的命令清单。

  • 线程对象可以认为是把 李四、王五叫过来了。

  • 而调用 start()方法,就是喊一声:"劳动啦!",线程才真正独立去执行了。

流程图说明

  1. 学员 A(新线程):执行逻辑、响应主线程的通知、A 线程结束。

  2. 老师(主线程):开始新线程、去查看任务结果、等待 A 线程结束、汇总到评分系统。

调用 start 方法,才真的在操作系统的层面创建出一个线程。


变量捕获与 lambda / 匿名内部类

**写作局部变量,此时无法编译通过~~**​

变量捕获:lambda / 匿名内部类

捕获的变量,必须是 final/ 事实 final

错误示例

java 复制代码
boolean running = true;
Thread t = new Thread(() -> {
    while (running) { // 编译错误:Variable used in lambda expression should be final or effectively final
        System.out.println("hello thread");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    System.out.println("t 线程退出");
});
t.start();

**写作成员变量,情况就不一样~~**​

触发的做法不再是"变量捕获",而是"内部变量向外部变成了类"。

正确示例(使用成员变量)

java 复制代码
private static boolean running = true;

public static void main(String[] args) {
    Thread t = new Thread(() -> {
        while (running) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("t 线程退出");
    });
    t.start();

    // 主线程中,让用户选择是否让 t 线程终止
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入整数,0 表示不让 t 线程终止:");
    int n = sc.nextInt();
    if (n == 0) {
        running = false;
    }
}

说明

  • lambda 本质上是基于函数式接口的匿名内部类。

C++ 的考虑:程序自行负责保证变量的生命周期匹配~~

Java 的考虑:变量的生命周期由 JVM 托管,更加安全~~

一旦修改了局部变量的生命周期可能出现不一致,代码更乱。

为了避免混乱,Java 直接禁止修改~~


Thread 提供更靠谱的方案实现上述的效果

获取当前线程的引用

这个 lambda 在哪个线程中执行的,得到的引用就是哪个线程 Thread引用~~

java 复制代码
while (!Thread.currentThread().isInterrupted()) {
    System.out.println("hello thread");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}
System.out.println("t 线程退出");

示例完整代码

java 复制代码
package thread;

import java.util.Scanner;

public class Demo9 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            Thread cur = Thread.currentThread();
            while (!cur.isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();

        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入整数表示让线程 t 结束:");
        int n = scanner.nextInt();
        if (n == 0) {
            // 这个方法不光可以设置标志位,还能唤醒 sleep 等导致线程阻塞的方法
            // 会使 sleep 漏出异常,InterruptedException
            t.interrupt();
        }
    }
}

执行逻辑说明

  • 执行这个代码的时候,一定是先针对 Thread t中的参数,先进行求值。

  • 在执行 Thread的构造方法。

  • 在把 Thread构造方法的结果,赋值给 t(定义 + 符合)

图示说明

  • Thread cur = Thread.currentThread();

    this表示当前对象

    currentThread表示当前线程
    Thread 对象内部,封装了一个 boolean 变量
    效果

  • 这个和手动修改标志位类似。

  • 还额外做了其他操作,把 sleep这样的方法唤醒。

异常信息(将来常用)

java 复制代码
Exception in thread "t 线程" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
    at thread.Demo9.lambda$main$0(Demo9.java:14)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException: sleep interrupted
    at java.base/java.lang.Thread.sleep(Native Method)
    at thread.Demo9.lambda$main$0(Demo9.java:11)
    ... 1 more

Process finished with exit code 0


日常开发中 catch 中的逻辑一般不会这么写~~

IDEA 生成的爆栈代码~~

java 复制代码
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}

实现开发中 catch 根据阻塞的不同,采取不同的动作

  1. 尝试重试~~

  2. 记录错误 / 日志~~(记到日志文件,写入到日志服务器中)

  3. 触发报警 / 短信~~(钉钉群、企业微信、邮件...)

相关推荐
苦逼的猿宝1 小时前
IT技术交流和分享平台的设计与实现(源码+论文)
java·毕业设计·springboot·计算机毕业设计
摇滚侠1 小时前
IDEA 需要修改的配置 开发工具
java·ide·intellij-idea
2601_957786771 小时前
企业矩阵运营的“三段论“:管号、产内容、获线索——全链路系统的价值拆解
java·前端·矩阵·多平台管理
海的透彻1 小时前
jmeter预制处理器JSR223-加解密
开发语言·jmeter·sm2·jsr233
asyxchenchong8881 小时前
R+VIC 模型融合实践技术应用及未来气候变化模型预测
开发语言·r语言
Run_Teenage1 小时前
算法模板:输入输出,并查集
java·开发语言·算法
一 乐1 小时前
公交线路查询系统|基于Java+vue公交线路查询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·公交线路查询系统
AllData公司负责人2 小时前
亲测丝滑,体验跃迁|AllData通过集成开源项目Datart,让数据可视化一目了然
java·大数据·数据库·python·数据可视化·数据视图·datart
未若君雅裁2 小时前
RabbitMQ 高可用机制:普通集群、镜像队列与仲裁队列
java·微服务·rabbitmq·java-rabbitmq