
我的个人主页
我的专栏: 人工智能领域、java-数据结构、Javase、C语言,MySQL,JavaEE初阶,希望能帮助到大家!!! 点赞👍收藏❤


文章目录
- 一:初识线程(Thread)
-
- 1.1:线程的概念
- 1.2:为什么需要线程?
- 1.3:那么进程和线程的区别是什么?
- [1.4:Java 的线程和操作系统线程的关系](#1.4:Java 的线程和操作系统线程的关系)
- 二:多线程程序的编写
-
- [2.1:继承 Thread 类](#2.1:继承 Thread 类)
- [2.2:实现 Runnable 接口](#2.2:实现 Runnable 接口)
- [2.3:匿名内部类创建 Thread 子类对象](#2.3:匿名内部类创建 Thread 子类对象)
- [2.4:匿名内部类创建 Runnable 子类对象](#2.4:匿名内部类创建 Runnable 子类对象)
- [2.5:lambda 表达式创建 Runnable 子类对象](#2.5:lambda 表达式创建 Runnable 子类对象)
- 三:在多线程下-增加了运行速度
-
- 四:Thread类及常见的方法
-
- 4.1:Thread常见的构造方法
- 4.2:Thread几个常见的属性
- [4.3:启动一个线程 - start()](#4.3:启动一个线程 - start())
- 4.4:中断一个线程
- 4.5:线程等待-join()
- 五:线程的状态
-
- [5.1:线程的状态是一个枚举类型 Thread.State](#5.1:线程的状态是一个枚举类型 Thread.State)
一:初识线程(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 类
java
package ThreadDemo;
class MyThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new MyThread();
thread.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}

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

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

右键点击以管理员身份运行,选择我们正在运行的线程,点击连接即可


其中main 就是我们的主线程,Thread-0就是我们通过MyThread类创建的线程,其他的线程都是我们jvm自带的,来负责对线程进行一些背后的操作。
点击main可以看到详细信息

这里是使用thread.start()情况下当我们使用thread.run()就没有Thread-0线程了。

2.2:实现 Runnable 接口
java
package ThreadDemo;
class MyRunnable implements Runnable{
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class demo2 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new MyRunnable();
Thread thread=new Thread(runnable);
thread.start();
while(true){
System.out.println("hello main");
thread.sleep(1000);
}
}
}

注意:
runnable 没有 start 方法.
要想启动线程, 需要搭配 Thread. runnable.start();
2.3:匿名内部类创建 Thread 子类对象
java
package ThreadDemo;
public class demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(){
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
2.4:匿名内部类创建 Runnable 子类对象
java
package ThreadDemo;
public class demo4 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new Runnable(){
public void run(){
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread t=new Thread(runnable);
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
2.5:lambda 表达式创建 Runnable 子类对象
java
package ThreadDemo;
public class demo4 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new Runnable(){
public void run(){
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread t=new Thread(runnable);
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
三:在多线程下-增加了运行速度
使用 System.nanoTime() 可记录系统的纳秒级时间戳:
- 串行(serial):单线程依次完成一系列运算
- 并行(concurrency):通过两个线程同时执行相同运算
以一个代码为例:
java
package ThreadDemo;
public class demo6 {
private static final long count = 10_0000_0000;
public static void main(String[] args) throws InterruptedException {
// 使⽤并发⽅式
concurrency();
// 使⽤串⾏⽅式
serial();
}
private static void concurrency() throws InterruptedException {
long begin = System.nanoTime();
// 利⽤⼀个线程计算 a 的值
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
}
});
thread.start();
// 主线程内计算 b 的值
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
// 等待 thread 线程运⾏结束
thread.join();
// 统计耗时
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("并发: %f 毫秒%n", ms);
}
private static void serial() {
// 全部在主线程内计算 a、b 的值
long begin = System.nanoTime();
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("串⾏: %f 毫秒%n", ms);
}
}

需要注意的是 :多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的。
四:Thread类及常见的方法
在 Java 中,Thread 类是 JVM 用来管理线程的核心类。每个线程在 JVM 中都对应一个唯一的 Thread 对象,通过这个对象可以对线程的状态、行为进行控制和管理。
4.1:Thread常见的构造方法
| 构造方法声明 | 参数说明 | 用途 |
|---|---|---|
Thread() |
无参数 | 创建一个线程对象,需通过重写 run() 方法定义线程任务(默认线程名由 JVM 生成,如 Thread-0)。 |
Thread(Runnable target) |
target:实现 Runnable 接口的对象(封装线程任务) |
创建线程并关联任务,推荐使用(解耦线程与任务逻辑,避免单继承限制)。 |
Thread(String name) |
name:线程名称(字符串) |
创建线程并指定名称,便于调试(如日志输出、线程监控时区分不同线程)。 |
Thread(Runnable target, String name) |
target:任务对象;name:线程名称 |
同时指定任务和线程名称,兼顾任务解耦与调试便利性。 |
Thread(ThreadGroup group, Runnable target) |
group:线程组;target:任务对象 |
将线程加入指定线程组(线程组用于批量管理线程,如统一设置优先级、中断等)。 |
说明:
- 最常用的构造方法是
Thread(Runnable target)和Thread(Runnable target, String name),通过传入Runnable对象分离任务逻辑,更符合面向对象设计。 - 线程名称可通过
setName()后续修改,但建议在构造时指定,便于早期调试。 - 线程组(
ThreadGroup)主要用于线程的批量管理,日常开发中使用较少,除非需要对一组线程进行统一操作(如销毁所有子线程)。
那么如何为一个线程修改一下名字呢?以下面代码为例细述
java
public class demo6{
public static void main(String[] args) {
Thread t=new Thread(()->{
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"我的线程1");
t.start();
}
}
通过jconsole就可以看到我刚刚修改的线程名字

注意:为什么这个线程中没有看到main,是因为main线程已经执行完了。
- 在这个代码中start执行完毕,main方法就运行完了
- main方法结束,对应的main线程就结束了
- 也就是对于一个线程来说,线程对应的"入口方法"执行完毕,对应的线程就会自动销毁。
4.2:Thread几个常见的属性
| 属性 | 获取方法 | 说明 |
|---|---|---|
| ID | getId() |
线程的唯一标识,不同线程不会重复 |
| 名称 | getName() |
用于调试工具区分线程,可自定义 |
| 状态 | getState() |
表示线程当前所处的情况(如 NEW、RUNNABLE、TERMINATED 等) |
| 优先级 | getPriority() |
优先级高的线程理论上更容易被调度(范围 1~10,默认 5) |
| 是否后台线程 | isDaemon() |
JVM会在一个进程的所有非后台线程结束后,才会结束运行。 |
| 是否存活 | isAlive() |
简单理解为 run 方法是否运行结束 |
| 是否被中断 | isInterrupted() |
标识线程是否被中断(需结合 interrupt() 方法协作处理) |
这里讲一下是否为后台进程,引入思考那么前台线程又是什么?
前台线程:线程没有运行完,进程就不会结束(即线程可以阻止进程结束)
后台线程:线程没有运行完,进程可以结束(即线程不能阻止进程结束)
应用场景:当一个线程做的任务很重要,必须要完成这个任务,这样就应该设置为前台线程。
如果一个线程做的任务无关紧要/周期性无期限执行,就可以设置为后台线程。
我们可以通过.setDaemon()设置线程为前台还是后台,但是这个操作必须在start之前。
以下面代码为例,详细描述一下现成的存活isAlive()
java
package ThreadDemo;
public class demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
System.out.println("是否存活:"+t.isAlive());
Thread.sleep(4000);
System.out.println("是否存活:"+t.isAlive());
}
}

可以看到判断线程是否存活,看线程的状态即可。
- 线程正在执行,isAlive()就是true的。
- 线程执行完毕,isAlive()就是false的。
4.3:启动一个线程 - start()
以这个代码为例
java
package ThreadDemo;
public class demo8 {
public static void main(String[] args) {
Thread t=new Thread(()->{
System.out.println("hello thread");
});
t.start();
System.out.println("hello main");
}
}

当我们执行几次结果后发现,都是先打印hello main 在打印hello thread那么是不是都是一直这个顺序呢?
当start后,main线程和t线程两个执行流,是并发执行 的关系。由于操作系统对于线程的调度是随机的,所有也有可能出现先打印hello thread的情况。
重点:一个线程对象 只能start一次。
当一个线程对象出现一次以上start就会出现如下图所示报错

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

此处针对lambda表达式定义,实际上实在new Thread()之前的,就会出现t没有进行初始化 的报错信息。
我们可以使用Thread提供的一个静态方法currentThread
java
package ThreadDemo;
public class demo9 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
//由于currentThread方法是在start之后在执行的
//并且是在t线程中执行的,返回的结果就是指向t线程对象的引用
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
}
});
t.start();
Thread.sleep(2000);
//在main线程中尝试中止t线程
t.interrupt();
}
}
这样就可以使我们的t线程结束
但是我们在循环中加入sleep后

就会触发异常

在这里如果t线程正在休眠,此时的main调用Interrupt方法,就会把sleep提前给唤醒。sleep被提前唤醒,就触发异常,就把isInterrupted标志位给重置为了false。这样我们的线程就不会结束
那么在这种情况下该怎么结束线程呢?
①:可以在e.printStackTrace();后面加上一个break,让线程立即结束
java
package ThreadDemo;
public class demo9 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
//由于currentThread方法是在start之后在执行的
//并且是在t线程中执行的,返回的结果就是指向t线程对象的引用
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
t.start();
Thread.sleep(2000);
//在main线程中尝试中止t线程
t.interrupt();
}
}

当然也可以把这个e.printStackTrace();注释掉,这样异常就不会打印了
②:也可以通过ideal自动生成的catch: throw new RuntimeException(e),也可以在break之前在添加其他逻辑
这几种方式,本质上都是t线程自己决定自己是否要终止,相当于main只是对t提供了一个"建议",而不是:"强制执行的".
使用Interrupt方法的时候:
- 1:t 线程没有进行sleep等阻塞操作,t 的
isInterrupted()方法返回true,通过循环结束t 线程。 - 2:t 线程进行sleep等阻塞操作 ,还是会返回true,但是sleep如果被提前唤醒InterruptException,同时也会把
isInterrupted()返回的结果设置为false,此时就需要手动去决定是否要结束线程。
4.5:线程等待-join()
mian线程等待t 线程结束
java
package ThreadDemo;
public class demo10 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for(int i=0;i<3;i++){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
System.out.println("主线程等待之前");
t.join();
System.out.println("主线程等待之后");
}
}

t 线程等待 main 线程结束
java
package ThreadDemo;
public class demo11 {
public static void main(String[] args) throws InterruptedException {
Thread myThread=Thread.currentThread();
Thread t=new Thread(()->{
try {
System.out.println("t线程等待之前");
myThread.join();
System.out.println("t线程等待之后");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
for(int i=0;i<10;i++){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}

注意这里需要获取main线程的引用:Thread myThread=Thread.currentThread();
- 哪个线程调用的join,该线程就是等的一方
- join前面是那个引用,该线程就是被等的一方
join默认的是死等,但是join还有重载版本,可以指定最大的等待时间
| 方法声明 | 等待时间参数说明 |
|---|---|
void join(long millis) |
millis:等待时间(单位:毫秒),0 表示无限等待(与无参 join() 效果一致) |
void join(long millis, int nanos) |
millis:毫秒数(主单位),nanos:纳秒数(范围 0-999999,辅助精度),两者总和为总等待时间 |
以上面代码为例,加入3000毫秒的最大等待时间

五:线程的状态
5.1:线程的状态是一个枚举类型 Thread.State
在 Java 中,线程(Thread)的生命周期包含以下 6 种状态,定义在 Thread.State 枚举类中,如下图所示:
| 状态名称 | 说明 |
|---|---|
NEW(新建) |
线程对象已创建,但尚未调用 start() 方法,未开始执行。 |
RUNNABLE(可运行) |
线程已调用 start() 方法,正在 JVM 中执行,或等待 CPU 资源(就绪状态)。 |
BLOCKED(阻塞) |
线程因竞争同步锁(synchronized)被阻塞,等待获取锁。 |
WAITING(等待) |
线程无限期等待另一个线程的特定操作(如 wait()、join() 无参版),需被显式唤醒。 |
TIMED_WAITING(计时等待) |
线程在指定时间内等待(如 sleep(time)、wait(time)、join(time)),超时后自动唤醒。 |
TERMINATED(终止) |
线程执行完毕(run() 方法结束)或因异常终止。 |
可通过 Thread.getState() 方法获取线程当前状态。
java
package ThreadDemo;
public class demo12 {
public static void main(String[] args) {
Thread t=new Thread(()->{
});
System.out.println(t.getState());
t.start();
t.join();
System.out.println(t.getState());
}
}

可以看到还没有开始start时候就是NEW状态,线程执行完了就是TERMINATED
RUNNABLE,当在t.start(); 后加入
System.out.println(t.getState()+" "+t.isAlive());
此时就可以看到RUNNABLE状态,代码中不触发阻塞类操作,都是RUNNABLE状态

其他三个都是阻塞状态,以下面代码为例
java
package ThreadDemo;
public class demo12 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
System.out.println(t.getState()+" "+t.isAlive());
t.start();
System.out.println(t.getState()+" "+t.isAlive());
t.join();
System.out.println(t.getState()+" "+t.isAlive());
}
}
再利用jconsole就可以看到我们main线程的状态

以及Thread-0的状态,此时t线程使在join操作所以是 TIMED_WAITING状态。


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