Java 中的线程是并发编程的核心。下面从基础概念到实际使用,系统地讲一讲。
一、进程 vs 线程
- 进程:操作系统分配资源的基本单位,每个进程有独立的内存空间。
- 线程 :CPU 调度的基本单位,同一进程内的多个线程共享进程的内存空间 (堆、方法区),但各自有独立的栈 和程序计数器。
线程更轻量,创建和切换开销比进程小,但共享内存也带来了线程安全问题。
二、创建线程的方式
1. 继承 Thread 类
java
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行中:" + Thread.currentThread().getName());
}
}
new MyThread().start(); // 注意是 start() 不是 run()
start()会启动新线程并让 JVM 调用run();- 直接调用
run()只是普通方法调用,不会开新线程。
2. 实现 Runnable 接口(推荐)
java
Runnable task = () -> System.out.println("任务执行");
new Thread(task).start();
优点:避免单继承限制,任务与线程解耦。
3. 实现 Callable + FutureTask
java
Callable<Integer> callable = () -> 1 + 1;
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
Integer result = futureTask.get(); // 阻塞获取返回值
相比 Runnable,可以有返回值、可以抛异常。
4. 线程池(实际开发最常用)
java
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> System.out.println("池中线程执行"));
pool.shutdown();
生产环境推荐用 ThreadPoolExecutor 手动创建,避免 Executors 的坑(如无界队列导致 OOM)。
三、线程的生命周期
Java 线程状态定义在 Thread.State 枚举中,共 6 种:
| 状态 | 说明 |
|---|---|
| NEW | 创建了但未调用 start() |
| RUNNABLE | 可运行(包括正在运行和就绪等待 CPU) |
| BLOCKED | 等待获取 synchronized 锁 |
| WAITING | 无限期等待(wait()、join()、park()) |
| TIMED_WAITING | 限时等待(sleep、wait(ms)、join(ms)) |
| TERMINATED | 已结束 |
转换示意:
NEW → start() → RUNNABLE ⇄ BLOCKED/WAITING/TIMED_WAITING → TERMINATED
四、常用方法
| 方法 | 作用 |
|---|---|
start() |
启动线程 |
run() |
线程执行体 |
sleep(ms) |
让当前线程休眠,不释放锁 |
join() |
等待该线程结束 |
yield() |
让出 CPU,回到就绪状态 |
interrupt() |
中断线程(设置中断标志) |
isInterrupted() |
检查中断标志 |
setDaemon(true) |
设为守护线程(JVM 退出时不管它) |
⚠️ 已废弃 :stop()、suspend()、resume(),容易造成死锁或数据不一致。
五、线程安全问题
多个线程访问共享可变数据时可能出错。经典例子:
java
class Counter {
private int count = 0;
public void increment() { count++; } // 非原子操作
}
count++ 实际是「读-改-写」三步,多线程下会丢失更新。
解决方案
1. synchronized(内置锁)
java
public synchronized void increment() { count++; }
// 或
synchronized (lock) { count++; }
2. Lock(显式锁)
java
Lock lock = new ReentrantLock();
lock.lock();
try { count++; } finally { lock.unlock(); }
比 synchronized 更灵活(可中断、可超时、公平锁)。
3. 原子类(CAS)
java
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();
基于 CAS,无锁,性能好。
4. volatile
保证可见性 和有序性 ,但不保证原子性。适合状态标志位。
六、线程间通信
wait / notify / notifyAll
必须在 synchronized 块中调用,且锁对象一致。
java
synchronized (lock) {
while (!condition) lock.wait(); // 释放锁并等待
// 处理
lock.notifyAll(); // 唤醒其他线程
}
⚠️ 用 while 而非 if,防止虚假唤醒。
其他工具
CountDownLatch:等待 N 个任务完成CyclicBarrier:一组线程互相等待Semaphore:控制并发数量BlockingQueue:生产者-消费者模型
七、JMM(Java 内存模型)
理解线程必须了解 JMM:
- 每个线程有自己的工作内存 ,共享变量存在主内存;
- 线程读写变量要经过工作内存,导致可见性问题;
- happens-before 规则保证某些操作的可见性(如锁的释放先于获取、volatile 写先于读)。
八、最佳实践
- 优先用线程池,不要频繁 new Thread。
- 给线程起名字,方便排查问题。
- 缩小锁范围,避免锁嵌套。
- 优先用并发容器(ConcurrentHashMap、CopyOnWriteArrayList)代替同步包装。
- 避免死锁:按固定顺序获取锁、使用超时锁。
- 捕获异常 :线程内未捕获异常会导致线程静默死亡,可设置
UncaughtExceptionHandler。