1 什么是进程与线程
1.1 进程(Process)
1.1.1 进程的定义
进程是操作系统进行资源分配的基本单位。当一个程序被启动后,操作系统会为这个程序分配独立的系统资源,这些资源包括内存空间、文件句柄、网络端口等。
例如:运行一段 Java 程序时,启动 Java 虚拟机(JVM),JVM 就是一个操作系统中的进程。
1.1.2 进程的内存结构
一个进程拥有自己独立的内存空间,通常包括以下区域:
代码区(Code Segment):存放程序的字节码或机器指令。
数据区(Data Segment):存放静态变量、全局变量。
堆(Heap):存放动态分配的对象,例如 Java 中 new 出的对象。
栈(Stack):每个线程一个独立栈,存放方法调用、局部变量、返回地址等。
系统资源:文件描述符、网络连接、I/O 管道等。
由于这些资源是独立的,一个进程的崩溃通常不会影响到另一个进程,从而增强了系统稳定性。
1.1.3 进程的特点
彼此独立,拥有独立的内存空间。
创建、销毁开销较大。
通信复杂,一般使用 IPC(管道、共享内存、消息队列、套接字)。
1.2 线程(Thread)
1.2.1 线程的定义
线程是程序执行的最小单位,是 CPU 调度的基本单位。一个进程内部可以包含多个线程,多线程共享进程的资源,使程序可以并发执行多个任务。
1.2.2 Java 多线程共享与独立的资源
同进程内多个线程共享:
堆:对象实例统一放在堆中,共享访问。
方法区 / 元空间:类信息、常量池等被所有线程共享。
线程独有:
栈:每个线程独立的调用栈,彼此互不影响。
程序计数器 PC:用于指示当前执行的位置。
寄存器:由 CPU 控制。
1.2.3 使用多线程的原因
利用多核 CPU 并行执行能力。
当某个线程阻塞(例如 I/O)时,不会影响其他线程继续执行。
能构建更加流畅、高性能的应用程序。
1.3 进程与线程的区别总结
关键:Java 多线程的本质在于 JVM 进程内创建多个线程,共享同一堆内存,提高程序并发能力。
2 Java 中的线程模型
2.1 JVM 线程与操作系统线程的关系
Java 使用的是 1:1 线程模型(HotSpot VM)。
每启动一个 Java Thread,会在底层创建一个对应的操作系统原生线程。
线程的调度、创建、挂起、恢复由系统执行,而不是 JVM 自己调度。
每个线程的栈、寄存器等资源由操作系统提供。
2.1.1 这种模型的优点
可以最大程度利用操作系统的成熟调度算法。
多核 CPU 下有更强的并行能力。
性能上限高。
2.1.2 这种模型的缺点
创建线程成本较高,因为需要调用系统 API 创建原生线程。
大量线程会造成过多的上下文切换,影响性能。
无法无限创建线程,因此实际应用中广泛使用线程池。
3 线程的生命周期(Thread Life Cycle)
Java 使用 Thread.State 表示线程状态,共 6 种。
3.1 生命周期状态图
NEW → RUNNABLE → TERMINATED
↓ ↑
WAITING ←→ TIMED_WAITING
↓
BLOCKED
3.2 各状态的进入原因
3.2.1 WAITING(无限等待)
进入原因:
Object.wait()
Thread.join()
LockSupport.park()
特点:
wait 会释放锁
线程一直等待,直到被 notify、unpark、join 结束等方式唤醒
3.2.2 TIMED_WAITING(定时等待)
进入原因:
Thread.sleep(ms)
Object.wait(ms)
Thread.join(ms)
LockSupport.parkNanos()
特点:
自动唤醒,不需要其他线程介入
不会释放锁(sleep 的情况)
3.2.3 BLOCKED(锁阻塞)
进入原因:
线程尝试进入 synchronized 修饰的代码块/方法
锁被其他线程持有
特点:
不释放 CPU
等待锁释放后自动进入 RUNNABLE 状态
4 创建线程的三种方式
4.1 方式一:继承 Thread 类
示例:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行: " + Thread.currentThread().getName());
}
}
public class Demo {
public static void main(String[] args) {
new MyThread().start();
}
}
4.1.1 特点
简单易用。
受限于 Java 单继承机制,不推荐在大型工程中使用。
4.1.2 常见误区:run() 和 start() 的区别
run() 是普通方法调用
start() 才能创建新线程并让 JVM 调度执行 run()
4.2 方式二:实现 Runnable 接口(最常用)
示例:
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("执行任务");
}
}
public class Demo {
public static void main(String[] args) {
Thread t = new Thread(new MyTask());
t.start();
}
}
4.2.1 优势
避免单继承限制。
将任务与线程对象解耦,更适合线程池使用。
可实现多个任务共享同一个 Runnable 对象。
4.3 方式三:Callable + FutureTask(可返回结果)
示例:
Callable<Integer> callable = () -> {
return 123;
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
System.out.println("结果:" + futureTask.get());
4.3.1 特点
call() 方法可以返回值。
可以抛出异常。
futureTask.get() 是阻塞的,用于等待返回结果。
常用于多线程计算场景,如分治任务。
5 Thread 常用方法详解
5.1 start()
启动一个新的线程。
不能重复调用,否则抛异常 IllegalThreadStateException。
JVM 会调用操作系统 API 创建原生线程。
5.2 run()
普通方法。
不能创建新线程。
5.3 sleep()
暂停当前线程指定时间。
不释放锁。
常用于模拟延迟或者控制节奏。
示例:
Thread.sleep(1000);
5.4 join()
让当前线程等待另一个线程执行完。
示例:
t.join();
常用于主线程等待子线程结束。
5.5 yield()
当前线程提示调度器让出 CPU。
可能没有效果,实际使用较少。
5.6 interrupt()
不是强制中断线程。
是给线程设置一个中断标记位 = true。
线程需要自己检查标记,决定是否退出。
示例:
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
}
6 用户线程与守护线程
6.1 守护线程
典型示例:垃圾回收线程(GC)
设置方式:
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
特点:
守护线程是辅助性质的。
如果所有用户线程结束,JVM 会自动退出,无论守护线程跑没跑完。
7 线程优先级
7.1 设置方式
thread.setPriority(Thread.MAX_PRIORITY); // 10
范围:1 ~ 10
说明:
优先级只是调度建议。
不保证高优先级的线程一定先运行。
8 本章总结
Java 线程底层是操作系统线程,使用 1:1 映射模型。
线程共享堆和方法区,但各有自己的栈。
线程有 6 种状态,RUNNABLE/WAITING/BLOCKED 是常见关键状态。
创建线程的推荐方式是 Runnable 和 Callable。
start() 才能真正启动线程。
sleep() 不释放锁,而 wait() 会释放锁。
interrupt() 是协作中断机制,不会强制终止线程。
守护线程不会阻止 JVM 退出。