Java 多线程基础:进程线程概念与 Thread 类详解
本文介绍操作系统中进程与线程的基本概念及区别,涵盖进程控制块(PCB)、资源分配与调度机制。重点讲解 Java 中创建线程的四种方式(继承 Thread、实现 Runnable、匿名内部类、Lambda),以及线程的生命周期管理方法,包括 start 与 run 的区别、前台后台线程设置、休眠、中断处理及线程等待(join)。内容旨在帮助开发者理解 Java 并发编程的基础原理与核心 API 使用。

本文介绍操作系统中进程与线程的基本概念及区别,涵盖进程控制块(PCB)、资源分配与调度机制。重点讲解 Java 中创建线程的四种方式(继承 Thread、实现 Runnable、匿名内部类、Lambda),以及线程的生命周期管理方法,包括 start 与 run 的区别、前台后台线程设置、休眠、中断处理及线程等待(join)。内容旨在帮助开发者理解 Java 并发编程的基础原理与核心 API 使用。

1950 年代,计算机系统通常是单任务的。早期计算机一次只能执行一个程序,需要人工切换。这种设计简单但效率低下。
1960 年代,多任务系统的概念开始萌芽。早期的大型机操作系统如 IBM 的 OS/360 引入了分时技术,允许多个用户同时使用计算机资源。虽然计算机实际一次只能干一件事,但靠这种'闪电式切换',用户感觉电脑在同时处理多个任务。
1970 年代,Unix 操作系统诞生,采用了多任务设计。Unix 通过进程调度和时间片轮转机制,允许多个程序并发执行。这一设计成为现代多任务系统的基础。
**单任务 (进程) 系统:**同一时间只能运行一个程序或任务,任务必须按顺序完成。用户需等待当前任务结束后才能启动新任务。系统资源由一个任务独占,缺乏并发能力,适用于简单应用场景。
**多任务 (进程) 系统:**允许同时运行多个程序或任务,通过时间片轮转或优先级调度实现并发。
**进程 (Process)/任务 (Task):**进程是计算机中正在运行的程序的实例 (应用程序与进程的关系近似于 Java 中类和对象的关系),是操作系统进行资源分配的基本单位。
当一个进程被创建的时候,操作系统会为它创建一个或多个 PCB(Process Control Block 进程控制块),类似 C 语言的结构体/Java 中的类,PCB 里面会存放该进程的各种属性:
**文件描述符表:**文件描述符表通常是一个数组或链表,每个节点对应一个文件描述符,进程通过文件描述符来访问文件,实现进程对文件的增删查改等操作。
**内存指针:**指向的空间就是该进程创建时所申请的内存空间,指明了这个空间中哪些位置存放数据,哪些位置存放指令/代码。
**PID:**进程的 id,可以认为是进程的身份标识,每个进程的 id 是独一无二的,不能重复。
优点:
缺点:
**线程 (Thread)/轻量级进程:**由操作系统调度,不用额外分配资源,是操作系统调度执行的基本单位。一个进程可以包含多个线程,所有线程共享进程的资源 (由线程控制块 TCB 管理),但每个线程拥有独立的执行栈和程序计数器。
优点:
缺点:
构造方法如下:
1. 直接继承 Thread 类
// Thread 类实现了 Runnable 接口,所以需要重写 run 方法
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("Hello MyThread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new MyThread();
// 启动线程
thread.start();
}
}
2. 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("Hello MyRunnable");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
// 启动线程
thread.start();
}
}
3. 匿名内部类创建 Runnable 子类对象
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
// 匿名内部类
@Override
public void run() {
while (true) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
thread.start();
}
}
4. lambda 表达式创建 Runnable 子类对象
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {});
// 获取线程 id
System.out.println(thread.getId());
// 获取线程 name
System.out.println(thread.getName());
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
// false, 线程还没启动
System.out.println(thread.isAlive());
thread.start();
// true, 线程正在执行任务
System.out.println(thread.isAlive());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// false, 线程结束
System.out.println(thread.isAlive());
}
}
**前台线程:**会阻止进程终止,即使主线程执行完毕,只要存在前台线程仍在运行,进程会继续运行直到所有前台线程完成任务。默认情况下,主线程是前台线程,通过 Thread 类创建的线程默认也是前台线程。
**后台线程:**不会阻止进程终止,当所有前台线程结束时,后台线程会被自动终止,无论是否完成任务。
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
System.out.println("Hello Thread");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
// 把 thread 设置为后台线程,不会阻止主线程的结束,thread 线程会伴随 main 线程的结束而结束
thread.setDaemon(true);
thread.start();
System.out.println("Hello Main");
}
}
**运行结果:**Hello Main
**start 方法:**用于启动一个新线程,新线程会执行 run 方法中的代码。每个线程只能调用一次 start,重复调用会抛出 IllegalThreadStateException (无论该线程是否执行完任务)。
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Hello Thread");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
// 开启新线程,thread 线程执行 run 方法
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("Hello Main");
Thread.sleep(100);
}
}
}
**run 方法:**直接调用 run 方法不会创建新线程,而是在当前线程中执行 run 方法的代码。
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Hello Thread");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
// main 线程执行 run 方法
thread.run();
for (int i = 0; i < 5; i++) {
System.out.println("Hello Main");
Thread.sleep(100);
}
}
}
**Thread.sleep:**是 Java 中用于暂停当前线程执行的一个静态方法。它允许线程在指定的时间内进入休眠状态,期间不会占用 CPU 资源。休眠结束后,线程会重新进入就绪 (RUNNABLE) 状态,等待调度执行。
// 毫秒级精确度,需要处理 InterruptedException(编译时异常)
public static native void sleep(long millis) throws InterruptedException;
// 纳秒级精确度
public static void sleep(long millis, int nanos) throws InterruptedException;
**知识回顾:**main 方法中:既可以 throws 声明异常,也可以 try-catch 异常。 run 方法中:只能 try-catch 异常,因为子类重写父类方法时不能修改父类方法的方法签名。
在继承 Thread 类的自定义线程类中,可以通过 this 关键字直接访问当前线程的属性和方法。
class MyThread extends Thread {
@Override
public void run() {
System.out.println(this.getId() + " " + this.getName());
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
在使用 Runnable 接口或 Lambda 表达式创建线程时,无法直接使用 this 获取线程信息,因为 this 指向的是 Runnable 实例而非线程对象。应调用 Thread.currentThread() 获取当前线程的引用。
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
Thread t1 = Thread.currentThread();
System.out.println(t1.getId() + " " + t1.getName());
});
thread.start();
}
}
结合以上两点,变量捕获机制允许一个线程获取另一个线程中的 final 变量。在上述代码中,thread 线程捕获到 main 线程中的 isQuit 变量,本质上是将 isQuit 变量拷贝到 thread 线程中,而不是直接访问 main 的栈帧 (避免因变量的生命周期而可能产生的线程安全问题)。如果允许匿名内部类或 lambda 表达式访问并修改局部变量,就会导致变量逃逸 (escape),即变量的引用或值被传递到栈帧之外造成栈中变量的内存地址泄漏,这种情况会引发严重的安全问题,因为原本被 Java 线程模型规定为线程私有的数据可能会被并发访问。
通过修改类变量来终止线程
public class ThreadDemo {
public static boolean isQuit = false;
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!isQuit) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("Over Thread");
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
isQuit = true;
}
}
**interrupt():**是 Java 中用于中断线程的方法。它的核心作用是向目标线程发送一个中断信号,但并不会直接终止线程的执行。需要主动检查中断状态并决定如何响应中断。
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println("Over Thread");
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 终止线程,并唤醒线程中的 sleep
thread.interrupt();
}
}
**join 方法:**用于等待线程执行完成。调用某个线程的 join 方法后,当前线程会被阻塞,直到目标线程执行完毕。
| 方法 | 说明 |
|---|---|
| join() | 等待线程无限期执行完成 |
| join(long millis) | 等待线程执行完成,但最多等待 millis 毫秒 |
| join(long millis, int nanos) | 等待线程执行完成,但最多等待 millis 毫秒+nanos 纳秒 |
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = 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);
}
}
System.out.println("Over Thread");
});
thread.start();
// main 等待 thread 线程执行完毕
thread.join();
// main 线程恢复执行
System.out.println("Hello Main");
}
}
执行结果: Hello Thread Hello Thread Hello Thread Over Thread Hello Main

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online