一、基础概念
1.1 进程与线程
进程是操作系统资源分配的基本单位,拥有独立的内存空间。线程是 CPU 调度的基本单位,同一进程内的多个线程共享进程的堆内存和方法区,但每个线程拥有独立的程序计数器、虚拟机栈和本地方法栈。
1.2 并发与并行
- 并发(Concurrency):多个任务在同一时间段内交替执行,宏观上同时进行,微观上串行。
- 并行(Parallelism):多个任务在同一时刻真正同时执行,需要多核 CPU 支持。
1.3 上下文切换
CPU 通过时间片轮转调度线程,切换时需要保存当前线程状态并加载下一线程状态,产生上下文切换开销。减少不必要的线程创建和锁竞争是降低切换开销的关键。
二、Java 线程的创建与生命周期
2.1 创建线程的三种方式
-
继承 Thread 类
class MyThread extends Thread { @Override public void run() { System.out.println("Thread running"); } } -
实现 Runnable 接口
class MyRunnable implements Runnable { @Override public void run() { System.out.println("Runnable running"); } } -
实现 Callable 接口(带返回值,配合 FutureTask)
Callable<String> callable = () -> "result"; FutureTask<String> futureTask = new FutureTask<>(callable); new Thread(futureTask).start();
推荐优先使用 Runnable 或 Callable,避免单继承限制,实现代码与执行解耦。
2.2 线程生命周期
Java 线程在 java.lang.Thread.State 中定义了六种状态:
| 状态 | 说明 |
|---|---|
| NEW | 新建,未调用 start() |
| RUNNABLE | 就绪或运行中,等待 CPU 调度 |
| BLOCKED | 阻塞,等待 monitor lock(如进入 synchronized 块) |
| WAITING | 无限期等待,如调用 wait()、join() 不超时 |
| TIMED_WAITING | 限期等待,如 sleep(ms)、wait(ms)、join(ms) |
| TERMINATED | 执行完毕或异常退出 |
三、Java 内存模型(JMM)
3.1 主内存与工作内存
JMM 规定所有变量存储在主内存,每个线程拥有自己的工作内存。线程对变量的读写必须在工作内存中进行,再通过特定协议与主内存同步。
3.2 三大特性
- 原子性:一个操作不可中断。基本类型赋值具有原子性(long/double 在 64 位 JVM 下通常也是),但复合操作如
i++不保证原子性。 - 可见性:一个线程修改共享变量后,其他线程能立即看到最新值。
- 有序性:禁止指令重排序导致的逻辑错误。
3.3 happens-before 规则
JMM 定义了无需同步即可保证可见性的偏序关系,包括:
- 程序次序规则
- 锁规则(unlock happens-before 后序 lock)
- volatile 规则(写 happens-before 后序读)
- 传递性
四、线程同步机制
4.1 synchronized 关键字
- 修饰实例方法:锁当前对象实例(
this) - 修饰静态方法:锁类的 Class 对象
- 修饰代码块:显式指定锁对象
public synchronized void method() {} // 实例锁
public static synchronized void method() {} // 类锁
synchronized(obj) { } // 对象锁
synchronized 底层依赖对象头的 Mark Word 和 monitor 机制,JDK 6 后引入偏向锁、轻量级锁、重量级锁的锁升级过程,减少无竞争场景下的性能损耗。
4.2 volatile 关键字
- 保证变量对所有线程的可见性
- 禁止指令重排序
- 不保证原子性
适用场景:状态标志位、单例模式的双重检查锁定(DCL)。
4.3 Lock 接口与 ReentrantLock
java.util.concurrent.locks.Lock 提供比 synchronized 更灵活的锁操作:
Lock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}
特性:
- 可中断锁:
lockInterruptibly() - 超时获取:
tryLock(long time, TimeUnit unit) - 公平锁:构造参数
true启用公平策略 - 条件队列:
newCondition()实现精确唤醒
4.4 读写锁 ReentrantReadWriteLock
- 读锁:共享锁,多个读线程可同时持有
- 写锁:独占锁,写线程独占访问
适用于读多写少的场景,提升并发性能。JDK 8 后推荐使用性能更优的 StampedLock。
4.5 StampedLock
提供三种访问模式:
- 写锁:独占
- 悲观读锁:共享,但会阻塞写锁
- 乐观读:无锁读取,读取后需通过
validate(stamp)确认数据未被修改
五、线程间通信
5.1 wait / notify / notifyAll
- 必须在同步代码块内调用
wait()释放锁并进入 WAITING 状态notify()唤醒单个等待线程,notifyAll()唤醒全部
经典模式:生产者-消费者
synchronized (queue) {
while (queue.isEmpty()) {
queue.wait();
}
queue.poll();
queue.notifyAll();
}
5.2 Condition
ReentrantLock 的配套条件队列,支持多个等待集:
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
相比 wait/notify,Condition 可以精确控制唤醒对象,避免全部唤醒带来的性能损耗。
六、线程池与 Executor 框架
6.1 线程池优势
- 降低线程创建与销毁的开销
- 提高响应速度
- 便于线程管理与监控
- 控制并发线程数量
6.2 ThreadPoolExecutor 核心参数
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit,
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
6.3 常用任务队列
| 队列 | 特性 |
|---|---|
| SynchronousQueue | 直接提交,无缓冲 |
| LinkedBlockingQueue | 无界队列,可能导致 OOM |
| ArrayBlockingQueue | 有界数组队列,需指定容量 |
6.4 拒绝策略
- AbortPolicy(默认):抛出异常
- CallerRunsPolicy:由调用线程执行任务
- DiscardPolicy:静默丢弃
- DiscardOldestPolicy:丢弃队列最老任务
6.5 内置线程池(慎用)
Executors.newFixedThreadPool(n):固定核心数,无界队列,有 OOM 风险Executors.newCachedThreadPool():允许无限创建线程,有资源耗尽风险Executors.newSingleThreadExecutor():单线程,无界队列
阿里巴巴 Java 开发手册建议通过
ThreadPoolExecutor手动创建线程池,明确参数含义。
6.6 生命周期
- RUNNING:接受新任务并处理队列任务
- SHUTDOWN:不接受新任务,但处理队列中剩余任务
- STOP:不接受新任务,不处理队列任务,中断正在执行的任务
- TIDYING:所有任务终止,执行
terminated() - TERMINATED:
terminated()执行完毕
七、常用并发工具类
7.1 CountDownLatch
允许一个或多个线程等待其他线程完成操作。
CountDownLatch latch = new CountDownLatch(3);
// 每个子任务执行完调用 latch.countDown()
latch.await(); // 主线程等待
7.2 CyclicBarrier
使一组线程互相等待到达公共屏障点,可循环使用。
CyclicBarrier barrier = new CyclicBarrier(3);
barrier.await();
7.3 Semaphore
控制同时访问某资源的线程数量(限流)。
Semaphore semaphore = new Semaphore(10);
semaphore.acquire(); // 获取许可
semaphore.release(); // 释放许可
7.4 CompletableFuture
JDK 8 引入的异步编程工具,支持链式组合、异常处理、多任务组合:
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(this::process)
.thenAccept(this::save)
.exceptionally(ex -> { log.error(ex); return null; });
7.5 Exchanger
用于两个线程之间交换数据,适用遗传算法、管道设计等场景。
八、原子类与 CAS
java.util.concurrent.atomic 包提供无锁原子操作类:
AtomicInteger、AtomicLong、AtomicBooleanAtomicReference、AtomicStampedReference(解决 ABA 问题)LongAdder:高并发下比AtomicLong性能更优,采用分段累加
底层依赖 CAS(Compare-And-Swap) 操作,由 CPU 指令(如 cmpxchg)保证原子性。
CAS 三大问题:
- ABA 问题:值从 A 变 B 又变 A,无法感知中间变化。解决方案:
AtomicStampedReference - 自旋开销:高竞争下长时间自旋消耗 CPU
- 只能保证单个变量原子性
九、并发集合
| 非线程安全 | 线程安全替代 |
|---|---|
| ArrayList | CopyOnWriteArrayList |
| HashMap | ConcurrentHashMap |
| HashSet | ConcurrentHashMap.newKeySet() / CopyOnWriteArraySet |
| TreeMap | ConcurrentSkipListMap |
9.1 ConcurrentHashMap
- JDK 7:分段锁(Segment),默认 16 段
- JDK 8:CAS + synchronized 锁单个桶头节点,结构类似 HashMap(数组+链表+红黑树)
9.2 CopyOnWriteArrayList
读操作无锁,写操作复制新数组。适用于读多写极少场景,写操作开销大。
十、Fork/Join 框架
JDK 7 引入的并行计算框架,基于**工作窃取(Work-Stealing)**算法:
class MyTask extends RecursiveTask<Integer> {
@Override
protected Integer compute() {
if (taskTooSmall()) {
return computeDirectly();
}
MyTask left = new MyTask(...);
MyTask right = new MyTask(...);
left.fork();
right.fork();
return left.join() + right.join();
}
}
ForkJoinPool.commonPool() 被 CompletableFuture、Parallel Stream 等底层复用。
十一、ThreadLocal
为每个线程提供独立的变量副本,实现线程隔离。
ThreadLocal<Integer> local = ThreadLocal.withInitial(() -> 0);
local.set(1);
int value = local.get();
内存泄漏风险:ThreadLocal 作为 Key 被 ThreadLocalMap 持有,若线程长期存活(如线程池中的线程)且未手动 remove(),会导致 Value 无法回收。
最佳实践:
- 使用
try { ... } finally { local.remove(); } - JDK 8 后可用
InheritableThreadLocal实现子线程继承父线程值 - 在线程池场景下尤其注意清理
十二、死锁与避免策略
12.1 死锁产生的四个必要条件
- 互斥条件:资源独占
- 请求与保持:持有资源同时请求新资源
- 不剥夺条件:资源只能主动释放
- 循环等待条件:形成等待环路
12.2 避免策略
- 资源一次性分配:一次性申请所有所需资源
- 有序加锁:所有线程按固定顺序获取锁
- 超时放弃:使用
tryLock(timeout) - 死锁检测:通过线程 dump 分析
十三、总结
Java 多线程编程从底层的线程创建、生命周期管理,到 JMM 内存模型的理解,再到 synchronized、volatile、Lock 等同步机制,以及线程池、原子类、并发集合和 Fork/Join 框架,构成了完整的并发知识体系。
关键要点:
- 理解 JMM 的可见性、原子性、有序性
- 优先使用线程池管理线程生命周期
- 选择合适的同步机制(synchronized 简单够用,Lock 更灵活)
- 高并发下善用原子类和 LongAdder
- 注意 ThreadLocal 的内存泄漏问题
- 避免使用 Executors 的便捷方法创建无界线程池
掌握这些知识点,可以帮助开发者在实际项目中编写出高效、稳定、可维护的多线程程序。
评论区