要理解 Java 并发编程,必须掌握JMM(Java Memory Model,Java 内存模型) 和JUC(java.util.concurrent,并发工具包) 这两个核心概念。JMM 是底层规范,定义了多线程交互的规则;JUC 是基于 JMM 实现的高层工具,简化了并发编程。下面分别详解:
一、JMM(Java Memory Model):多线程通信的底层规范
JMM 是抽象的内存模型 ,它并不直接对应物理硬件(如 CPU 缓存、主内存),而是定义了一套规则,用于规范线程如何通过内存进行交互,解决多线程环境下可见性、有序性、原子性问题。
1. JMM 的核心目标
多线程并发时,线程间的通信(数据共享)依赖于内存。JMM 的目标是:
- 定义线程和主内存之间的交互方式(线程如何读取 / 写入内存);
- 解决因 CPU 缓存、指令重排序等硬件 / 编译器优化导致的多线程数据不一致问题。
2. JMM 的核心结构
JMM 抽象了 "主内存" 和 "线程工作内存" 两个概念:
-
主内存:所有线程共享的内存区域,存储共享变量(实例字段、静态字段等)。
-
线程工作内存:每个线程私有的内存区域,存储主内存中共享变量的副本。线程对变量的读写操作,需先将变量从主内存加载到工作内存,修改后再同步回主内存。
(注意:这是逻辑抽象,并非物理上的内存划分,实际对应 CPU 缓存、寄存器等硬件)
3. JMM 解决的三大问题
多线程并发的核心问题是可见性、有序性、原子性,JMM 通过规则和关键字保证这些特性:
(1)可见性(Visibility)
问题 :线程 A 修改了共享变量,线程 B 可能无法立即看到最新值(因工作内存未同步到主内存)。
解决:
volatile
:强制变量读写直接操作主内存,通过 CPU 缓存一致性协议(如 MESI)通知其他线程缓存失效。synchronized
/Lock
:解锁前必须将变量同步回主内存,加锁时必须从主内存加载最新值。final
:被final
修饰的变量初始化后不可修改,一旦初始化完成,其他线程可见。
(2)有序性(Ordering)
问题 :编译器或 CPU 为优化性能,可能对指令重排序(单线程不影响结果,但多线程可能出错)。
解决:
volatile
:通过插入内存屏障(Memory Barrier)禁止重排序(写后加 "Store 屏障",读前加 "Load 屏障")。synchronized
/Lock
:同步块内的指令视为一个整体,不会与块外指令重排序。- Happens-Before 规则 :JMM 定义的天然有序性规则,无需显式同步(如 "程序顺序规则":单线程内代码按顺序执行;"volatile 规则":
volatile
写操作先于读操作)。
(3)原子性(Atomicity)
问题 :一个操作(如i++
)若包含多个步骤(读 - 改 - 写),多线程并发时可能被打断,导致结果错误。
解决:
synchronized
/Lock
:通过排他锁保证同步块内操作的原子性(同一时间只有一个线程执行)。- 原子类(如
AtomicInteger
):基于 CPU 的 CAS(Compare-And-Swap)指令,实现无锁原子操作。
4. 关键:Happens-Before 规则
JMM 通过 "Happens-Before" 规则定义两个操作的执行顺序,无需显式同步即可保证有序性:
- 程序顺序规则:单线程内,前面的操作 Happens-Before 后面的操作。
- volatile 规则:
volatile
变量的写操作 Happens-Before 后续的读操作。 - 锁规则:解锁操作 Happens-Before 后续的加锁操作。
- 线程启动规则:
Thread.start()
Happens-Before 线程内的所有操作。 - 线程终止规则:线程内的所有操作 Happens-Before 其他线程检测到该线程终止(如
Thread.join()
返回)。
二、JUC(java.util.concurrent):并发编程工具包
JUC 是 Java 5 引入的并发工具类库,基于 JMM 的规则实现,封装了复杂的并发逻辑,简化了多线程编程。核心组件包括:
1. 线程池(Executor 框架)
线程的创建 / 销毁成本高,线程池通过复用线程提高性能,核心类:
ExecutorService
:线程池接口,定义了提交任务的方法(如submit()
)。ThreadPoolExecutor
:线程池核心实现类,可自定义核心线程数、最大线程数、队列等参数。- 工具类
Executors
:提供快捷创建线程池的方法(如newFixedThreadPool()
、newCachedThreadPool()
),但实际开发中建议手动创建ThreadPoolExecutor
以避免资源耗尽风险。
2. 并发集合(线程安全的集合)
解决HashMap
、ArrayList
等线程不安全集合的并发问题:
ConcurrentHashMap
:线程安全的HashMap
,Java 8 后基于 CAS+synchronized 实现,支持高并发读写。CopyOnWriteArrayList
:读操作无锁,写操作复制一份新数组修改后替换旧数组,适合读多写少场景。ConcurrentLinkedQueue
:无锁的并发队列,基于 CAS 实现,高效支持多线程入队 / 出队。BlockingQueue
:阻塞队列(如ArrayBlockingQueue
、LinkedBlockingQueue
),常用于生产者 - 消费者模型(队列为空时读阻塞,满时写阻塞)。
3. 同步工具类
用于协调多线程的执行顺序:
CountDownLatch
:倒计时门闩,让主线程等待多个子线程完成后再执行(countDown()
减计数,await()
阻塞等待计数为 0)。CyclicBarrier
:循环屏障,让多个线程到达屏障点后再一起继续执行(可重复使用,适合多轮协作)。Semaphore
:信号量,限制同时访问资源的线程数(acquire()
获取许可,release()
释放许可)。Phaser
:阶段同步器,支持动态调整参与线程数,适合分阶段的任务协作。
4. 原子操作类(java.util.concurrent.atomic)
基于 CAS 实现无锁原子操作,避免synchronized
的性能开销:
- 基本类型:
AtomicInteger
、AtomicLong
、AtomicBoolean
(支持getAndIncrement()
等原子操作)。 - 引用类型:
AtomicReference
(原子更新对象引用)。 - 数组类型:
AtomicIntegerArray
(原子更新数组元素)。 - 字段更新器:
AtomicIntegerFieldUpdater
(原子更新对象的某个字段,需字段为volatile
)。
5. 锁框架(java.util.concurrent.locks)
补充synchronized
的不足,提供更灵活的锁机制:
-
Lock
接口:定义锁的基本操作(lock()
、unlock()
、tryLock()
等),实现类有:ReentrantLock
:可重入锁(支持公平锁 / 非公平锁,synchronized
是隐式可重入)。ReentrantReadWriteLock
:读写锁,允许多个读线程并发,写线程独占(读多写少场景优化性能)。
-
Condition
:与Lock
配合使用,实现类似Object.wait()
/notify()
的等待 - 通知机制,支持多条件队列。
6. 其他工具
ThreadLocalRandom
:线程安全的随机数生成器,比Random
性能更高。Fork/Join
框架:基于分治思想的并行计算框架(ForkJoinPool
、RecursiveTask
),适合处理大任务拆分后的并行计算。
三、JMM 与 JUC 的关系
-
JMM 是基础:定义了多线程内存交互的规则(可见性、有序性、原子性),是所有并发机制的理论依据。
-
JUC 是实现 :JUC 中的工具类(如
ReentrantLock
、AtomicInteger
)均基于 JMM 的规则实现,例如:volatile
依赖 JMM 的可见性和有序性保证;ReentrantLock
通过 AQS(AbstractQueuedSynchronizer)框架,结合 JMM 的内存屏障和 CAS 操作实现同步;- 并发集合通过
synchronized
、Lock
或 CAS 保证线程安全,底层依赖 JMM 的原子性和可见性规则。
总结
-
JMM:解决 "多线程如何安全通信" 的底层规范,核心是通过规则保证可见性、有序性、原子性,是并发编程的 "宪法"。
-
JUC:基于 JMM 实现的 "工具箱",封装了线程池、并发集合、锁等工具,让开发者无需深入底层即可写出安全高效的并发代码。
掌握两者的关系,能帮助你从本质理解 Java 并发机制,避免 "只会用 API,不懂原理" 的困境。