Java多线程:从基础到线程安全的完整指南
本文从最基础的线程概念入手,系统地梳理了 Java 中线程的定义、创建方式、状态流转以及线程安全问题。结合 JMM(Java 内存模型)的原理,深入讲解了多线程中的可见性、原子性、有序性问题,并补充了waitnotify的底层逻辑与配合方式。看完这篇,你会对多线程体系形成清晰的整体认知。线程(Thread)是轻量级的进程。每个线程都有自己独立的任务,但多个线程共享进程的系统资源。当多个线程同时访问
摘要:
本文从最基础的线程概念入手,系统地梳理了 Java 中线程的定义、创建方式、状态流转以及线程安全问题。结合 JMM(Java 内存模型)的原理,深入讲解了多线程中的可见性、原子性、有序性问题,并补充了 wait、notify 的底层逻辑与配合方式。看完这篇,你会对多线程体系形成清晰的整体认知。
一、什么是线程?
线程(Thread)是轻量级的进程。
每个线程都有自己独立的任务,但多个线程共享进程的系统资源。
二、进程与线程的区别
可以把进程比作一座工厂,而线程就是工厂里的生产线。
一个工厂(进程)里可以有多条生产线(线程),每条生产线都有自己的工作任务,比如一条做组装,一条做包装。
这些生产线虽然各自独立完成不同任务,但它们共享同一工厂的资源——使用同样的厂房、电力、设备和原材料。
因此,线程就像生产线,是进程内部的轻量级执行单位,负责在共享资源的基础上并行完成不同的工作。
二、进程与线程的区别
| 对比点 | 进程 | 线程 |
|---|---|---|
| 含义 | 正在运行的程序 | 进程中的执行单元 |
| 系统分配资源单位 | ✅ 最小单位 | ❌ 共享进程资源 |
| CPU 调度单位 | ❌ | ✅ 最小单位 |
| 影响范围 | 相互独立 | 线程之间会相互影响 |
一个进程中至少有一个线程(即主线程)。
当线程出问题时,可能会导致整个进程崩溃;但进程之间互不影响。
三、线程的创建方式
-
继承 Thread 类,重写
run()方法。class MyThread extends Thread { @Override public void run() { // 线程要执行的代码 for (int i = 0; i < 5; i++) { System.out.println("子线程正在运行:" + i); } } } public class Demo1 { public static void main(String[] args) { MyThread t = new MyThread(); // 创建线程对象 t.start(); // 启动线程(自动调用 run()) System.out.println("主线程结束"); } }重点描述“线程”的定义与行为本身。
-
实现 Runnable 接口,重写
run()方法。class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Runnable 线程运行中:" + i); } } } public class Demo2 { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); // 创建任务对象 Thread t = new Thread(mr); // 将任务交给线程执行 t.start(); System.out.println("主线程继续执行"); } }重点描述“任务逻辑”,推荐这种写法,更灵活。
-
匿名内部类方式
直接在创建线程时定义任务逻辑,更简洁。public class Demo3 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("匿名内部类线程运行中:" + i); } } }); t.start(); System.out.println("主线程执行完毕"); } } -
Lambda 表达式方式
由于 Runnable 是函数式接口,可以使用 Lambda 表达式快速创建线程。public class Demo4 { public static void main(String[] args) { Thread t = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println("Lambda 线程运行中:" + i); } }); t.start(); System.out.println("主线程继续执行"); } }
四、为什么要使用多线程?
主要目的:提升程序运行效率
-
当任务重、逻辑复杂时,多线程能有效并行执行,提升 CPU 利用率;
-
当任务轻、逻辑简单时,多线程反而会引入上下文切换开销,降低效率。
五、线程的六种状态
Java 在系统的 PCB(进程控制块)基础上做了封装,将线程状态划分为以下几种:
| 状态 | 含义 |
|---|---|
| NEW | 线程对象创建,但未启动(未调用 start()) |
| RUNNABLE | 包含“运行中 + 就绪”状态,随时可能被调度执行 |
| BLOCKED | 等待锁资源(synchronized)时的状态 |
| WAITING | 无期限等待,如 wait()、join() |
| TIMED_WAITING | 带超时等待,如 wait(time)、sleep(time)、join(time) |
| TERMINATED | 执行结束,系统资源释放 |
六、什么是线程安全?
当多个线程同时访问共享资源时,程序运行结果出现非预期情况,就发生了线程安全问题。
七、导致线程安全的三大原因
-
线程是抢占式执行的,不按顺序来;
-
多线程修改了同一个共享变量;
-
没有正确地保证:
-
原子性(一个操作要么全做,要么不做);
-
可见性(一个线程的修改能否被其他线程看到);
-
有序性(代码执行顺序是否和预期一致)。
-
八、JMM —— Java 内存模型
Java 内存模型规定:
每个线程都有自己的工作内存(相当于 CPU 的寄存器缓存)。
线程修改变量时,不能直接改主内存中的值,而是先复制到自己的工作内存中修改,之后再刷新回主内存。
JMM 要保证的三大特性:
-
原子性:操作不可被中断;
-
可见性:线程间修改能被及时看到;
-
有序性:防止 CPU 或编译器的指令重排。
九、解决线程安全的方式
1. synchronized
-
可以修饰方法或代码块;
-
解决原子性问题;
-
通过原子性间接保证了可见性;
-
但无法解决指令有序性问题。
2. volatile
-
修饰共享变量;
-
通过内存屏障保证可见性;
-
禁止指令重排序,保证有序性;
-
但不保证原子性。
十、wait、notify、notifyAll 的用法
-
这些方法都定义在 Object 类 中;
-
必须和
synchronized一起使用; -
调用
wait()的线程会:-
释放锁;
-
进入等待队列;
-
被
notify()或notifyAll()唤醒后重新竞争锁; -
拿到锁后从
wait()处继续执行。
-
| 方法 | 作用 |
|---|---|
| wait() | 当前线程进入等待状态(释放锁) |
| notify() | 随机唤醒一个正在等待的线程 |
| notifyAll() | 唤醒所有等待的线程 |
⚠️ 唤醒和等待的线程必须针对同一个锁对象,否则无法通信。
更多推荐


所有评论(0)