在 Java 并发编程中,volatile 仅能解决可见性和有序性问题,且无法保证原子性,因此面对复杂的线程安全场景,需要依赖更多机制。本文将系统梳理除 volatile 外,Java 中保证线程安全的核心方案,包括锁机制、原子类、不可变设计、线程封闭等,并分析各方案的适用场景与底层原理。
锁的本质是通过互斥性保证同一时间只有一个线程能执行临界区代码,从而解决原子性、可见性、有序性问题,是 Java 并发编程的基础。
synchronized 是 Java 原生的内置锁(也叫监视器锁),属于可重入锁,无需手动释放,JVM 会自动管理锁的获取与释放。
修饰类型 | 锁定对象 | 适用场景 |
|---|---|---|
实例方法 | 当前实例对象(this) | 保护实例级共享变量 |
静态方法 | 类对象(Class) | 保护静态共享变量 |
代码块 | 自定义对象/Class | 精准控制临界区,减少锁粒度 |
// 保护实例变量的同步方法
class SafeCounter {
privateint count = 0;
public synchronized void increment() {
count++; // 临界区:原子执行
}
public synchronized int getCount() {
return count;
}
}
// 同步代码块(减小锁粒度)
class SafeService {
privatefinal Object lock = new Object();
public void doSomething() {
// 仅锁定核心临界区,而非整个方法
synchronized (lock) {
// 核心操作
}
// 非临界区代码(无需锁定)
}
}
synchronized 并非简单的重量级锁,JVM 引入了锁升级机制优化性能:
Lock 是 JUC(java.util.concurrent)包提供的显式锁,相比 synchronized 更灵活,支持手动获取/释放、可中断、超时获取、公平锁等特性。
ReentrantLock(可重入锁)是 Lock 接口最常用的实现,核心特性:
synchronized 一致;lockInterruptibly() 响应线程中断,避免死锁;tryLock(long timeout, TimeUnit unit) 避免线程无限等待;Condition 实现更灵活的线程等待/唤醒(替代 wait()/notify())。import java.util.concurrent.locks.ReentrantLock;
class SafeCounter {
privateint count = 0;
privatefinal ReentrantLock lock = new ReentrantLock(); // 默认非公平锁
public void increment() {
lock.lock(); // 手动加锁
try {
count++; // 临界区
} finally {
lock.unlock(); // 必须在 finally 中释放锁,避免异常导致锁泄漏
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
// 公平锁 + 条件变量示例
class ConditionDemo {
privatefinal ReentrantLock lock = new ReentrantLock(true); // 公平锁
privatefinal Condition notEmpty = lock.newCondition();
privatefinal Condition notFull = lock.newCondition();
privatefinal String[] queue = new String[10];
privateint size = 0;
public void put(String item) throws InterruptedException {
lock.lock();
try {
while (size == queue.length) {
notFull.await(); // 队列满,等待
}
queue[size++] = item;
notEmpty.signal(); // 唤醒等待取数据的线程
} finally {
lock.unlock();
}
}
public String take() throws InterruptedException {
lock.lock();
try {
while (size == 0) {
notEmpty.await(); // 队列空,等待
}
String item = queue[0];
System.arraycopy(queue, 1, queue, 0, --size);
notFull.signal(); // 唤醒等待放数据的线程
return item;
} finally {
lock.unlock();
}
}
}
ReentrantReadWriteLock:读写锁,分离读/写操作,读操作共享(提高并发),写操作互斥;StampedLock:JDK 1.8 引入,支持乐观读,性能优于读写锁,适用于读多写少场景;LockSupport:底层工具类,通过 park()/unpark() 实现线程阻塞/唤醒,是 Lock 和线程池的基础。JUC 提供的 Atomic* 系列原子类,基于 CAS(Compare-And-Swap) 实现无锁化的原子操作,相比锁机制更轻量,性能更高(无线程阻塞/唤醒开销)。
CAS 是一种乐观锁机制,包含三个核心参数:
执行逻辑:如果内存地址 V 中的值等于预期值 A,则将其修改为 B,返回成功;否则返回失败(线程可重试)。CAS 是 CPU 原语指令,保证操作的原子性。
类型 | 实现类 | 适用场景 |
|---|---|---|
基本类型 | AtomicInteger、AtomicLong、AtomicBoolean | 原子更新int/long/boolean |
引用类型 | AtomicReference | 原子更新对象引用 |
数组类型 | AtomicIntegerArray、AtomicLongArray | 原子更新数组元素 |
字段更新器 | AtomicIntegerFieldUpdater | 原子更新对象的非静态字段 |
累加器(JDK 1.8+) | LongAdder、DoubleAdder | 高并发下的累加操作(性能优于 AtomicLong) |
import java.util.concurrent.atomic.AtomicInteger;
// 原子类实现线程安全计数
class AtomicCounter {
privatefinal AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子自增(等价于 count++)
}
// 复杂原子操作:CAS 重试
public void incrementIfLessThan100() {
while (true) {
int current = count.get();
if (current >= 100) {
break;
}
// CAS 替换:预期值为 current,更新为 current+1
if (count.compareAndSet(current, current + 1)) {
break;
}
}
}
public int getCount() {
return count.get();
}
}
// LongAdder 高并发累加(优于 AtomicLong)
import java.util.concurrent.atomic.LongAdder;
class HighConcurrencyCounter {
privatefinal LongAdder adder = new LongAdder();
public void add() {
adder.increment();
}
public long getTotal() {
return adder.sum();
}
}
原子类适用于简单原子操作(如计数、状态更新),相比锁机制:
AtomicStampedReference 解决)。不可变对象(Immutable Object)指创建后状态无法修改的对象,其核心特性是:对象的所有字段都是 final 的,且没有对外暴露的修改方法。由于状态不可变,多线程并发访问时无需任何同步措施,天然线程安全。
final 修饰,避免被继承(防止子类修改行为);final 修饰,保证初始化后不可修改;this 引用;Java 中的 String、Integer、Long 等包装类都是不可变对象,例如 String 的实现:
public final class String {
private final char value[]; // 不可变的字符数组
private final int hash; // 缓存哈希值
// 无 setter 方法,所有修改操作(如 substring、replace)都返回新对象
public String substring(int beginIndex) {
// 省略实现:返回新的 String 对象
}
}
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
// 不可变的用户信息类
publicfinalclass ImmutableUser {
privatefinal String id;
privatefinal String name;
privatefinal List<String> roles; // 可变集合,需特殊处理
// 构造方法初始化所有字段
public ImmutableUser(String id, String name, List<String> roles) {
this.id = id;
this.name = name;
// 拷贝传入的集合,避免外部修改
this.roles = Collections.unmodifiableList(new ArrayList<>(roles));
}
// 仅提供 getter 方法,无 setter
public String getId() {
return id;
}
public String getName() {
return name;
}
// 返回不可变视图,避免外部修改集合
public List<String> getRoles() {
return roles;
}
}
不可变对象适用于状态不频繁修改的场景,如配置信息、常量、事件对象等。优点是天然线程安全,无需同步;缺点是修改状态时需创建新对象,可能增加内存开销。
线程封闭(Thread Confinement)指将变量限制在单个线程内,不与其他线程共享,从而避免线程安全问题。核心思想是:没有共享,就没有竞争。
变量存储在线程的栈帧中,仅当前线程可访问(局部变量),是最自然的线程封闭方式。
示例:
// 局部变量仅在当前线程栈中,天然线程安全
public int calculate() {
int sum = 0; // 栈封闭变量
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
ThreadLocal 为每个线程提供独立的变量副本,线程访问 ThreadLocal 时仅操作自己的副本,互不干扰。
核心原理:
Thread 类包含 ThreadLocalMap 成员变量,存储当前线程的 ThreadLocal 变量副本;ThreadLocal 的 get()/set() 方法,本质是操作当前线程的 ThreadLocalMap。示例代码:
import java.text.SimpleDateFormat;
// ThreadLocal 解决 SimpleDateFormat 线程不安全问题
class DateUtil {
// 每个线程拥有独立的 SimpleDateFormat 副本
privatestaticfinal ThreadLocal<SimpleDateFormat> sdf = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
);
public static String format(long timestamp) {
return sdf.get().format(timestamp);
}
// 线程退出前清理,避免内存泄漏
public static void remove() {
sdf.remove();
}
}
注意事项:
ThreadLocal 可能导致内存泄漏(线程池中的线程长期存活,副本未清理),需在使用后调用 remove();InheritableThreadLocal 是 ThreadLocal 的子类,支持子线程继承父线程的变量副本。Java 提供了专门的并发容器,替代非线程安全的 ArrayList、HashMap 等,底层通过锁分段、CAS 等机制保证线程安全。
容器类型 | 实现类 | 核心特性 |
|---|---|---|
列表 | CopyOnWriteArrayList | 写时复制,读操作无锁,适用于读多写少 |
集合 | CopyOnWriteArraySet | 基于 CopyOnWriteArrayList 实现 |
映射 | ConcurrentHashMap | 分段锁(JDK 1.7)/ CAS + synchronized(JDK 1.8),高并发 HashMap |
队列 | ConcurrentLinkedQueue | 无锁队列,基于 CAS 实现 |
阻塞队列 | ArrayBlockingQueue、LinkedBlockingQueue | 支持阻塞的入队/出队,用于生产者-消费者模型 |
延迟队列 | DelayQueue | 按延迟时间排序的阻塞队列,适用于定时任务 |
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// ConcurrentHashMap 高并发读写
class ConcurrentMapDemo {
privatefinal Map<String, Integer> map = new ConcurrentHashMap<>();
public void put(String key, int value) {
map.put(key, value); // 原子操作,无需额外同步
}
public Integer get(String key) {
return map.get(key);
}
// 原子性的复合操作
public void increment(String key) {
map.compute(key, (k, v) -> v == null ? 1 : v + 1);
}
}
线程池本身不直接保证线程安全,但通过控制并发线程数、复用线程,避免因创建过多线程导致的资源耗尽,同时结合其他机制(如原子类、锁)可更安全地管理并发任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class ThreadPoolDemo {
// 创建固定大小的线程池
privatefinal ExecutorService executor = Executors.newFixedThreadPool(10);
public void submitTask(Runnable task) {
executor.submit(task);
}
// 优雅关闭线程池
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
机制类型 | 核心优势 | 劣势 | 适用场景 |
|---|---|---|---|
synchronized | 简单易用,JVM 自动管理 | 锁粒度较粗,性能一般 | 中小并发、临界区代码较短 |
Lock 接口 | 灵活(可中断、公平锁) | 需手动释放,易出错 | 高并发、复杂同步逻辑 |
原子类 | 无锁,性能高 | 仅支持简单操作 | 计数、状态更新、简单原子操作 |
不可变对象 | 天然安全,无同步开销 | 修改需创建新对象 | 状态稳定的对象(配置、常量) |
线程封闭 | 无竞争,性能最优 | 变量无法共享 | 局部变量、线程独有数据 |
并发容器 | 开箱即用,适配集合场景 | 部分容器写性能较低 | 高并发集合操作 |
线程池 | 控制并发数,复用线程 | 需合理配置参数 | 批量任务处理、异步执行 |
Java 提供了多层次的线程安全保障机制,核心可分为三类:
synchronized、Lock 等锁机制,通过互斥保证原子性;