可见性问题指线程修改共享变量后其他线程可能无法立即看到,根源在于工作内存与主内存不一致及指令重排序;volatile强制读写主内存并禁止重排序,synchronized和Lock通过内存屏障保障可见性与原子性,原子类和线程安全容器也提供可靠可见性保障。

Java多线程中的可见性问题,指的是一个线程对共享变量的修改,其他线程可能无法立即(甚至长时间)看到。这不是“没改”,而是“改了但别人看不见”——根源在于JVM的内存模型和硬件缓存机制。
为什么可见性会失效?
Java线程不直接读写主内存,而是各自持有工作内存(如CPU缓存、寄存器)。线程A修改变量后,可能只更新了自己本地副本,没及时刷回主内存;线程B读取时,仍从自己的缓存中拿旧值。
- JVM允许编译器重排序和处理器乱序执行,只要不改变单线程语义,但会破坏多线程下的观察一致性
- 没有同步机制时,写操作不一定对其他线程“发布”(publish)
- 常见于未加锁、未用volatile、未用原子类的普通字段读写场景
volatile关键字:最轻量的可见性保障
给变量加上volatile修饰,能强制每次读都从主内存加载,每次写都立即刷新到主内存,并禁止相关指令重排序。
- 适用于“一写多读”、状态标志位(如running = false)、简单布尔开关等场景
- 不能保证复合操作的原子性(比如count++仍是非线程安全的)
- 不适用于需要锁粒度控制或依赖上下文的复杂逻辑
同步块与锁:更彻底的可见性+原子性保证
进入synchronized代码块时,JVM会强制线程清空工作内存中该锁保护的变量副本;退出时,把修改全部刷新到主内存。这不仅解决可见性,还确保临界区内的操作不可分割。
立即学习“Java免费学习笔记(深入)”;
- 所有被同一把锁保护的共享变量,天然具备可见性保障
- 即使锁内只读不写,也能看到之前其他线程在同锁下写入的最新值
- ReentrantLock等显式锁同样提供相同的内存语义(需配合lock()/unlock()成对使用)
其他可靠手段:原子类与线程安全容器
java.util.concurrent.atomic包中的类(如AtomicInteger、AtomicReference)底层基于CAS和volatile,既保证可见性也保证某些操作的原子性。
- 适合计数器、状态切换、无锁编程等场景
- ConcurrentHashMap、CopyOnWriteArrayList等容器内部已做可见性处理,比手动加锁更高效
- 避免用Collections.synchronizedXxx包装非线程安全集合来“假装线程安全”——它只保方法级原子,不保复合操作可见性
基本上就这些。可见性不是玄学,本质是内存访问路径的可控性问题。用对volatile、锁或原子类,就能让“改了”真正等于“别人看到了”。










