问题历史:
当Java刚出现时,并没有关于内存和线程如何相互作用的正式描述。结果是相同的程序在不同的JVM上可能表现不同,导致难以捕捉的错误。2004年,Java内存模型(JMM)被添加到Java规范中,旨在严格定义线程如何看到变量的更新。
问题:
在没有明确的内存模型的情况下,写操作和读操作可能会被编译器或处理器交错,这导致从不同线程访问共享变量时出现意外行为。这可能导致竞争条件、可见性问题以及难以调试的同步错误。
解决方案:
JMM定义了规则:一个线程所做的更改何时对其他线程可见。它建立了_happens-before_的概念、同步器(synchronized,volatile,final)、内部锁,并限制在多线程环境中指令的重组。
代码示例:
class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int get() { return count; } }
关键特性:
为什么volatile不使操作原子化,而仅仅提供可见性?
Volatile仅保证线程之间的更改可见性,但不保证更改的原子性。例如,递增volatile变量不是原子操作,因为该操作由读取、修改和写入组成。
代码示例:
volatile int count = 0; count++; // 不是原子操作!
synchronized块能保证其外部的更改可见吗?
可以。在退出synchronized块后,块内所做的所有更改对其他线程可见,这些线程将进入同一对象监视器的块。
final字段的更改何时对其他线程可见?
只有当对象在传递其引用给其他线程之前完全构造时,final字段才能保证正确发布。例如,使用immutable对象。否则可能出现未初始化状态的可见性。
负面案例
开发者选择通过简单的volatile递增多线程访问网站的入口计数器
优点:
缺点:
积极案例
使用AtomicInteger作为计数器或synchronized方法
优点:
缺点: