Java 多线程
Java 从设计之初就把"并发"作为一等公民——Thread 类、synchronized、java.util.concurrent 包,让你能轻松写出多线程程序。这是 Java 区别于其他语言的一大杀手锏。本章带你掌握多线程的核心。
1. 进程与线程
先搞清两个概念:
- 进程(Process):操作系统分配资源的基本单位。一个运行的 JVM 就是一个进程。
- 线程(Thread):CPU 调度的基本单位,进程内的执行流。一个进程可以有多个线程,共享进程的内存。
多线程的好处:同时做多件事(下载视频时也能聊天)、充分利用多核 CPU(并行计算)、UI 不卡顿(后台任务跑在子线程)。
2. 创建线程的两种方式
Java 创建线程有三种方式,推荐用 Runnable / Lambda(灵活,可继承其他类):
// 方式一:继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
// Thread.currentThread().getName() 获取当前线程名
System.out.println(getName() + " 运行: " + i);
try {
Thread.sleep(500); // 睡眠 500ms
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
// 方式二:实现 Runnable 接口(推荐,可继承其他类)
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " 跑: " + i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// 启动方式一
MyThread t1 = new MyThread();
t1.setName("T1");
t1.start(); // ⚠️ 必须调 start(),不是 run()!
// 启动方式二
Thread t2 = new Thread(new MyRunnable(), "T2");
t2.start();
// 方式三:lambda(最简洁,Runnable 是函数式接口)
Thread t3 = new Thread(() -> System.out.println("lambda 线程在跑"), "T3");
t3.start();
// main 线程等待 t1 跑完
t1.join();
System.out.println("main 结束");
}
}关键点:
- 调
start()不是run()!start()启动新线程并调run();run()只是普通方法调用,在当前线程跑。 - 不要重复 start:一个 Thread 对象只能 start 一次,否则抛 IllegalThreadStateException。
- Thread 实现了 Runnable:继承 Thread 重写 run,本质也是 Runnable。
3. 线程的生命周期
线程有 6 种状态(Thread.State 枚举):
- NEW:创建了但没 start。
- RUNNABLE:正在运行或等待 CPU(就绪)。
- BLOCKED:等待 monitor 锁(synchronized)。
- WAITING:无限期等待(
wait()、join())。 - TIMED_WAITING:限时等待(
sleep(ms)、wait(ms))。 - TERMINATED:run 方法执行完毕。
4. synchronized:加锁保证原子性
多线程共享数据时,同步是核心难题。两个线程同时执行 count++ 可能丢失更新——因为 ++ 不是原子操作(读取+加1+写回三步,中间可能被打断)。synchronized 保证同一时刻只有一个线程能执行被锁的代码:
class Counter {
private int count = 0;
// ❌ 非线程安全:多个线程同时 ++ 会丢失更新
// public void increment() { count++; }
// ✅ 方法加 synchronized,同一时刻只有一个线程能进
public synchronized void increment() {
count++;
}
// ✅ 或只锁关键代码块(性能更好)
public void incrementBlock() {
synchronized (this) {
count++;
}
}
public int getCount() { return count; }
}
public class SyncDemo {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 启动 1000 个线程,每个 +1000 次
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
// 等所有线程结束
for (Thread t : threads) t.join();
// 期望 1000000,无锁情况下会小于这个值(丢失更新)
System.out.println("最终: " + counter.getCount());
}
}synchronized 的两种用法:
- 同步方法:
public synchronized void m(),锁 this(实例方法)或类对象(static 方法)。 - 同步代码块:
synchronized(obj) { ... },锁指定对象,粒度更细,性能更好。
5. volatile:保证可见性
volatile 解决的是可见性问题——一个线程改了变量,其他线程立即可见(避免读缓存)。但它不保证原子性,volatile int i; i++ 依然线程不安全:
class Flag {
// volatile:保证可见性(一个线程改了,其他线程立即可见)
// 但不保证原子性(对 ++ 操作无效)
public volatile boolean running = true;
}
public class VolatileDemo {
public static void main(String[] args) throws InterruptedException {
Flag flag = new Flag();
Thread worker = new Thread(() -> {
int count = 0;
// 不加 volatile,worker 可能一直读缓存,看不到 main 改的值
while (flag.running) {
count++;
}
System.out.println("worker 停了, 跑了 " + count + " 次");
});
worker.start();
Thread.sleep(100); // 让 worker 跑一会
flag.running = false; // 通知 worker 停
worker.join();
}
}volatile 的两个作用:
- 可见性:读直接从主内存读,写立即刷回主内存。
- 禁止指令重排序:编译器和 CPU 会优化指令顺序,volatile 禁止某些重排序(典型:双重检查锁定单例)。
6. 线程池:生产环境的标配
直接 new Thread() 在生产环境几乎看不到——线程池才是标配。它复用线程、控制并发数、管理任务队列:
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) throws Exception {
// ❌ 直接 new Thread 的缺点:无法控制并发数、创建销毁开销大
// ✅ 推荐用线程池:复用线程、控制并发数、管理任务队列
// 1. Executors 工厂方法(简单但生产环境慎用,固定参数更安全)
// ExecutorService pool = Executors.newFixedThreadPool(4);
// 2. ThreadPoolExecutor 显式配置(推荐)
ExecutorService pool = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲存活时间
new LinkedBlockingQueue<>(100), // 任务队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交 Runnable 任务(无返回值)
for (int i = 0; i < 5; i++) {
final int n = i;
pool.submit(() -> {
System.out.println("任务 " + n + " 在 " + Thread.currentThread().getName());
});
}
// 提交 Callable 任务(有返回值)
Future<Integer> future = pool.submit(() -> {
Thread.sleep(500);
return 42;
});
Integer result = future.get(); // 阻塞等待结果
System.out.println("Callable 结果: " + result);
// ⚠️ 关闭线程池!否则 JVM 不退出
pool.shutdown(); // 不接受新任务,已提交的跑完
// pool.shutdownNow(); // 尝试中断所有正在执行的任务
pool.awaitTermination(60, TimeUnit.SECONDS);
}
}线程池的关键参数:
- 核心线程数:常驻线程数。
- 最大线程数:任务多时最多开这么多线程。
- 任务队列:核心线程忙时,新任务排队。
- 拒绝策略:队列满、线程也满时怎么办(AbortPolicy 抛异常 / CallerRunsPolicy 调用者自己跑 / DiscardPolicy 丢弃)。
《阿里巴巴 Java 开发手册》建议:不要用 Executors.newFixedThreadPool 等工厂方法(可能 OOM),应该用 ThreadPoolExecutor 显式指定参数。
7. wait / notify:线程间协作
经典场景生产者-消费者:生产者生产数据放入队列,消费者取数据。队列满时生产者等,队列空时消费者等。wait() 让线程等待,notify() 唤醒:
import java.util.concurrent.*;
// 经典:生产者-消费者
class BoundedBuffer<T> {
private final Object lock = new Object();
private final java.util.LinkedList<T> queue = new java.util.LinkedList<>();
private final int capacity;
public BoundedBuffer(int capacity) { this.capacity = capacity; }
public void put(T item) throws InterruptedException {
synchronized (lock) {
// ⚠️ 必须用 while 不能 if(防止虚假唤醒)
while (queue.size() == capacity) {
lock.wait(); // 队列满,等待消费者消费
}
queue.add(item);
lock.notifyAll(); // 通知消费者
}
}
public T take() throws InterruptedException {
synchronized (lock) {
while (queue.isEmpty()) {
lock.wait(); // 队列空,等待生产者生产
}
T item = queue.removeFirst();
lock.notifyAll(); // 通知生产者
return item;
}
}
}
// 现代替代方案:用 BlockingQueue,无需手写 wait/notify
// BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
// queue.put("item"); // 满了自动阻塞
// queue.take(); // 空了自动阻塞几个易错点:
wait/notify必须在synchronized块内调用,否则抛IllegalMonitorStateException。- 用
while不用if判断条件——防止虚假唤醒。 - 优先
notifyAll而非notify(notify 只唤醒一个,可能选错)。 - 现代代码很少手写 wait/notify,用
BlockingQueue、Lock/Condition、CompletableFuture更安全。
8. java.util.concurrent:并发工具箱
Java 5 引入的 j.u.c 包提供了一堆高级并发工具,新项目应该优先用,不要手写 synchronized:
- 并发集合:
ConcurrentHashMap(线程安全 HashMap)、CopyOnWriteArrayList(读多写少)、BlockingQueue(生产者-消费者)。 - 同步工具:
CountDownLatch(等待 N 个线程完成)、Semaphore(限制并发数)、CyclicBarrier(集合点)。 - 原子类:
AtomicInteger、AtomicLong、AtomicReference,CAS 操作,比 synchronized 更轻。 - 锁:
ReentrantLock(比 synchronized 灵活)、ReadWriteLock(读写分离)、StampedLock(读优化)。 - 异步:
CompletableFuture(Java 8+,异步编程利器)、ForkJoinPool(分治)。
9. Java 21 虚拟线程(预览)
Java 21 LTS 正式 GA 了虚拟线程(Virtual Thread)——这是 Java 并发的革命性升级。它让你可以开几百万个线程而不耗内存:
- 普通线程(平台线程)对应 OS 线程,创建成本高(几 MB 栈空间),通常最多开几千个。
- 虚拟线程由 JVM 调度,挂在少量平台线程上,创建成本极低(KB 级)。
- 写法几乎不变:
Thread.startVirtualThread(() -> { ... });。 - 大幅简化高并发 Web 后端的编程模型,Go 的 goroutine 终于有了 Java 版。
小结
这一章你掌握了 Java 多线程的核心:Thread/Runnable、生命周期、synchronized、volatile、线程池、wait/notify、j.u.c 工具箱、虚拟线程。多线程是 Java 的难点也是优势——理解了它,你才能写出高效、稳定的并发程序。
恭喜!到这里你已经学完了整个 Java 入门系列 18 篇。从最简单的 Hello World,到面向对象、集合、异常、IO、多线程,你已经具备了独立写 Java 程序的能力。下一步建议:学 Spring Boot(后端开发)、Maven / Gradle(依赖管理)、MySQL + MyBatis(数据库)、JUnit(测试),进入实战!
← 上一篇 Java 输入输出
返回 Java 教程目录