java多线程并发安全问题典型表现为共享变量读写不一致,如未加锁的count++操作因“读-改-写”非原子性导致结果小于预期。

Java 中多线程并发安全问题,最典型的就是多个线程同时读写共享变量时出现数据不一致——比如计数器没加锁,最终结果远小于预期值。下面用简洁、可运行的代码直接模拟这个过程。
1. 模拟非线程安全的计数器
定义一个普通 int 变量 count,启动多个线程反复执行 count++(本质是读-改-写三步操作),不加任何同步手段:
public class UnsafeCounter {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[100];
for (int i = 0; i {
for (int j = 0; j
运行多次会发现输出不稳定,比如 99823、99511 等——这就是典型的竞态条件(Race Condition)。
2. 模拟可见性问题(volatile 不保证原子性)
仅用 volatile 修饰变量,能保证可见性,但无法解决 count++ 的原子性问题:
public class VolatileCounter {
public static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i {
for (int j = 0; j
3. 模拟指令重排序(双重检查锁 DCL 的经典陷阱)
在单例模式中,若未用 volatile 修饰 instance,可能导致线程拿到未初始化完成的对象:
public class DCLSingleton {
private static DCLSingleton instance;
private DCLSingleton() {
// 模拟耗时初始化(如加载配置)
try { Thread.sleep(1); } catch (InterruptedException e) {}
}
public static DCLSingleton getInstance() {
if (instance == null) {
synchronized (DCLSingleton.class) {
if (instance == null) {
instance = new DCLSingleton(); // 编译器/JVM 可能重排序:先赋值引用,再调构造
}
}
}
return instance;
}
}
// 测试类中启动多线程反复调用 getInstance(),可能触发 NPE 或部分初始化对象
虽然实际复现需特定 JVM 和压力,但该代码结构已暴露重排序风险——这也是并发安全中“正确性”容易被忽略的一环。
4. 快速修复对比(验证问题根源)
把上面任一例子加上同步机制,结果立刻稳定:
- 加 synchronized:方法或代码块加锁,确保同一时刻只有一个线程执行 count++
- 用 AtomicInteger:count = new AtomicInteger(0); 替换 count++ 为 count.incrementAndGet()
- volatile + final 修饰 instance:在 DCL 中给 instance 加 volatile,禁止重排序
修复后每次运行都输出预期值,反向印证了原始代码的问题所在。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











