一个类有一个全局变量 m,多线程对它进行增加操作,如何保证线程安全?
在多线程环境下对共享变量进行修改时,确保线程安全的关键是保证操作的原子性、可见性和有序性。以下是针对全局变量 m
的多线程自增操作的线程安全解决方案:
1. 使用 synchronized
关键字(悲观锁)
public class Counter {private static int m = 0;// 实例方法同步(锁住当前实例)public synchronized void increment() {m++;}// 静态方法同步(锁住类的 Class 对象)public static synchronized void staticIncrement() {m++;}
}
特点:
- 简单直接,但并发性能较低(锁粒度较大)。
- 适用于低竞争场景。
2. 使用 ReentrantLock
(显式锁)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Counter {private static int m = 0;private static final Lock lock = new ReentrantLock();public static void increment() {lock.lock();try {m++;} finally {lock.unlock();}}
}
特点:
- 更灵活(支持尝试锁、可中断锁等),但需手动管理锁的释放。
- 适用于需要细粒度控制的场景。
3. 使用原子类 AtomicInteger
(无锁,CAS 实现)推荐!
import java.util.concurrent.atomic.AtomicInteger;public class Counter {private static final AtomicInteger m = new AtomicInteger(0);public static void increment() {m.incrementAndGet(); // 原子性自增}
}
特点:
- 基于 CAS(Compare and Swap)实现无锁并发,性能高。
- 适用于高并发场景(如计数器、累加器)。
5. 使用 LongAdder
(高并发优化)
import java.util.concurrent.atomic.LongAdder;public class Counter {private static final LongAdder m = new LongAdder();public static void increment() {m.increment(); // 分段累加,减少竞争}public static long get() {return m.sum();}
}
特点:
- 在极高并发场景下性能优于
AtomicInteger
,但读取最终结果时需调用sum()
。 - 适用于写多读少的场景(如统计点击数)。
关键对比
方法 | 原理 | 性能 | 适用场景 |
| 悲观锁 | 低-中 | 低竞争、简单同步 |
| 显式锁 | 中 | 需要灵活控制锁 |
| CAS 无锁 | 高 | 高并发、简单原子操作 |
| 分段 CAS | 极高 | 超高并发写、最终一致性读 |
为什么 volatile
不能单独解决自增线程安全?
volatile
仅保证:
- 可见性:线程修改后其他线程立即可见新值。
- 有序性:禁止指令重排序优化。
但 m++
是非原子操作(包含读、改、写三步),多个线程可能同时读到相同的旧值并覆盖写入,导致结果错误。例如:
Thread1: 读取 m=5 → 计算 5+1=6 → 准备写入 6
Thread2: 读取 m=5 → 计算 5+1=6 → 写入 6
最终结果 m=6(实际应为 7)
最佳实践
- 优先选择原子类(如
AtomicInteger
或LongAdder
),兼顾性能和易用性。 - 若需复杂同步逻辑(如条件等待),使用
ReentrantLock
。 - 避免过度依赖
synchronized
,尤其是在高并发场景下。
通过合理选择同步机制,可以确保线程安全的同时最大化性能。