【JavaEE初阶】 多线程编程核心:解锁线程创建、方法与状态的创新实践密码
一:初识线程(Thread)
1.1:线程的概念
一个线程就是一条 “执行流”。每个线程各自按顺序执行自己的代码,多个线程之间则 “并发” 执行多份代码。线程(Thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
举一个例子:厨房做菜
想象一个繁忙的厨房正在准备顾客丰盛的晚餐,这个厨房就是一个 “进程”。
进程:一个正在执行的程序实例。它拥有独立的资源,如内存空间、文件句柄等。就像这个厨房,它拥有自己的空间、厨具、食材等资源。
线程:进程内部的一个独立执行流。一个进程可以包含多个线程,它们共享进程的资源。就像厨房里的厨师,每位厨师都是一个独立的“线程”。
这里就可以分为:
1.2:为什么需要线程?
首先,“并发编程”已成为“刚需”。单核CPU的发展遭遇瓶颈,要提升算力就需依赖多核CPU,而并发编程能更充分地利用多核CPU资源。
部分任务场景存在“等待IO”的情况,为了在等待IO的时间里处理其他工作,也需要用到并发编程。CPU可以切换到另一个线程去执行,避免了CPU时间的浪费。**就像一位厨师在等待水烧开时,可以先去切菜,而不是空等。
其次,尽管多进程也能实现并发编程,但线程比进程更轻量:
- 创建线程比创建进程更快;
- 销毁线程比销毁进程更快;
- 调度线程比调度进程更高效。
但是,即便线程已比进程轻量,人们仍不满足,进而发展出了“线程池”(ThreadPool)和“协程”(Coroutine)。
1.3:进程和线程的区别是什么?
- 进程包含线程,每个进程至少存在一个线程,即主线程。
- 进程之间不共享内存空间,而同一进程内的线程共享该进程的内存空间。
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
- 一个进程崩溃通常不会影响其他进程,但一个线程崩溃可能导致整个进程(及其中所有线程)一同崩溃。
1.4:Java 的线程和操作系统线程的关系
线程是操作系统层面的概念,其机制由操作系统内核实现,并向用户层提供API供调用(例如Linux的pthread库)。
Java标准库中的Thread类,可视为对操作系统提供的线程API进行了进一步的抽象与封装。
二:多线程程序的编写
2.1:继承 Thread 类

可以看到hello thread和hello main都打印出来了,但不是交替出现的。这里就体现了操作系统调度线程是随机的。每个线程都是独立的工作流,多个线程之间是并发执行的。
当我们将主线程中的`thread.start()`换成`thread.run()`,由于是死循环,只会看到打印hello thread:

> 在这里我们可以利用Java中的jconsole命令观察线程

右键点击以管理员身份运行,选择正在运行的线程,点击连接即可:
其中main是主线程,Thread-0是通过MyThread类创建的线程,其他线程是JVM自带的,负责线程相关的后台操作。

点击main可以看到详细信息:

这是使用thread.start()的情况,当使用thread.run()时,就没有Thread-0线程了:

2.2:实现 Runnable 接口

> 注意:
> runnable 没有 start 方法,要想启动线程,需要搭配 Thread,不能直接调用 runnable.start();
2.3:匿名内部类创建 Thread 子类对象
2.4:匿名内部类创建 Runnable 子类对象
2.5:lambda 表达式创建 Runnable 子类对象
---
三:多线程下的运行速度对比
使用 `System.nanoTime()` 可记录系统的纳秒级时间戳,通过对比串行和并发执行的耗时,观察多线程的速度优势:
- 串行(serial):单线程依次完成一系列运算
- 并行(concurrency):通过两个线程同时执行相同运算
示例代码
运行结果

> 注意:多线程并不一定就能提高速度,count 不同,实际的运行效果也会不同。
---
四:Thread类及常见方法
在 Java 中,`Thread` 类是 JVM 用来管理线程的核心类。**每个线程在 JVM 中都对应一个唯一的 `Thread` 对象**,通过这个对象可以对线程的状态、行为进行控制和管理。
4.1:Thread常见的构造方法

说明:
- 最常用的构造方法是 `Thread(Runnable target)` 和 `Thread(Runnable target, String name)`,通过传入 `Runnable` 对象分离任务逻辑,更符合面向对象设计。
- 线程名称可通过 `setName()` 后续修改,但建议在构造时指定,便于早期调试。
- 线程组(`ThreadGroup`)主要用于线程的批量管理,日常开发中使用较少,除非需要对一组线程进行统一操作(如销毁所有子线程)。
修改线程名称示例
通过jconsole可以看到修改后的线程名字:

> 注意:为什么这个线程中没有看到main?
> - 因为start执行完毕后,main方法就运行结束了。
> - main方法结束,对应的main线程就会销毁。
> - 线程对应的"入口方法"执行完毕,该线程就会自动销毁。
## 4.2:Thread几个常见的属性

后台线程与前台线程说明
- 前台线程:线程没有运行完,进程就不会结束(线程可以阻止进程结束)。
- 后台线程:线程没有运行完,进程也可以结束(线程不能阻止进程结束)。
应用场景
- 任务重要且必须完成时,设置为前台线程。
- 任务无关紧要或周期性无期限执行时,设置为后台线程。
设置后台线程示例
通过`.setDaemon()`设置线程为后台线程,该操作必须在**start**之前执行。
线程存活状态(isAlive())示例
![isAlive()执行结果]

> 线程正在执行时,isAlive()返回true;线程执行完毕后,isAlive()返回false。
4.3:启动一个线程 - start()
示例代码
![start()执行结果]

说明
- start()调用后,main线程和t线程是**并发执行**的关系。
- 操作系统对线程的调度是随机的,所以也可能出现先打印`hello thread`的情况。
- 重点:一个**线程对象**只能调用start**一次**,多次调用会报错。
多次start()报错示例
![多次start()报错]

4.4:中断一个线程
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记:
![中断标记示意图]

基本中断示例
使用Thread的静态方法`currentThread()`获取当前线程引用:
包含sleep()的中断处理
当线程处于sleep状态时,调用interrupt()会提前唤醒sleep并触发InterruptException,同时重置中断标记为false,此时需要手动处理线程终止:
中断逻辑说明
- 线程未阻塞时,interrupt()会设置中断标记为true,通过循环条件结束线程。
- 线程阻塞时(如sleep),interrupt()会触发异常并重置中断标记,需在catch中手动决定是否结束线程。
- 本质上,interrupt()是向线程发送“终止建议”,线程自行决定是否终止。
4.5:线程等待-join()
线程等待是指一个线程等待另一个线程执行完毕后再继续执行。
示例1:main线程等待t线程结束
示例2:t线程等待main线程结束
join()方法重载版本


join()核心逻辑
- 调用join()的线程是“等待方”。
- join()的调用对象是“被等待方”。
---
五:线程的状态
5.1:线程的状态枚举(Thread.State)
在 Java 中,线程(`Thread`)的生命周期包含 6 种状态,定义在 `Thread.State` 枚举类中:

可通过 `Thread.getState()` 方法获取线程当前状态。
状态示例代码

状态监控示例
通过jconsole观察线程状态:
- main线程状态(执行join()时):

- t线程状态(执行sleep()时):


状态转换关键点
- `NEW` → `RUNNABLE`:调用 `start()` 方法后进入可运行状态。
- `RUNNABLE` → `BLOCKED`:竞争同步锁失败时进入阻塞状态,获取锁后回到 `RUNNABLE`。
- `RUNNABLE` → `WAITING`/`TIMED_WAITING`:调用 `wait()`、`join()` 等方法时进入等待状态,被唤醒或超时后回到 `RUNNABLE`。
- 所有状态 → `TERMINATED`:线程执行完成或异常终止后进入终止状态,无法再切换到其他状态。
---



