volatile 关键字
先说结论. volatile 降低了并发风险而非解决了风险. volatile 并不是万能的且用且珍惜.
volatile 只能保证读当前值的准确. 但毕竟任意操作一定是另一条指令.
使用了volatile只是会降低分析复杂代码的成本.尽量保护 volatile 变量的准确性.
0x00 Java 中 volatile 的原理
可见性:
- 当一个线程修改了一个 volatile 变量的值,其他线程能够立即看到这个变化。
- 这是因为 JVM 规范要求 volatile 变量的写操作会将新值直接写入主内存,并且通知其他线程该变量已被修改。
- 同样,volatile 变量的读操作会从主内存中读取该变量的最新值。
禁止指令重排序:
- 在编译器和处理器优化代码时,可能会对代码进行重排序以提高执行效率。
- 但是这种重排序可能会导致多线程程序中的错误行为。
- 使用 volatile 可以禁止某些类型的指令重排序,保证代码按照程序员预期的顺序执行。
不保证原子性:
- 尽管 volatile 提供了可见性和有序性,但它并不保证复合操作的原子性。
- 例如,即使使用 volatile 定义了一个整型变量,对该变量进行加减操作仍然可能不是原子性的。
0x01 volatile 的作用
假设有一个 volatile 整型变量 counter:
1 |
|
即使 counter 被声明为 volatile,以下的自增操作 (counter++) 仍然不是原子的:
1 |
|
这个操作实际上包含了以下步骤:
- 读取 counter 的当前值。
- 将读取的值加一。
- 写回新的值到 counter。
如果多个线程同时执行 counter++, 因为不同的线程可能读取相同的旧值,然后各自独立地增加并写回相同的值,导致 counter 的值比预期的小。
0x02 volatile 在什么情况下能保证安全
变量的读写操作是原子的
对于基本类型如 int, long (当启用 64 位架构的原子性支持时), float, double (同样当启用 64 位架构的原子性支持时) 等,单一的读写操作是原子的。这意味着这些操作不会被其他线程中断。变量的更新不依赖于旧值
如果对 volatile 变量的操作不依赖于其当前值,那么这样的操作可以认为是安全的。例如,简单的赋值操作 x = y; 不依赖于 x 的当前值。变量不与其他状态一起参与不变约束
如果一个 volatile 变量的更新不依赖于其他变量的状态,那么它可以被视为安全的。例如,如果有两个变量 x 和 y,而 x 是 volatile 的,那么仅对 x 的更新是安全的,只要这个更新不依赖于 y 的值。仅有一个线程修改变量
即使多个线程读取 volatile 变量,只要有一个线程负责修改该变量,那么这个变量的更新也是安全的。这是因为 volatile 变量的更新对所有线程都是可见的,所以其他线程总是能看到最新的值。
注意事项
尽管 volatile 可以在上述情况下保证一定程度的安全性,但要注意以下几点:
- 复合操作:涉及多个步骤的操作(如 value++ 或 value += 1)并不是原子的,因此在多线程环境下可能会出现问题。
- 复合状态:如果多个 volatile 变量一起参与某个不变约束,那么需要更复杂的同步机制来确保线程安全。
- 初始化问题:volatile 不能保证对象的初始化顺序,因此在构造函数中初始化 volatile 字段时需要特别小心。
综上所述,volatile 关键词可以在满足特定条件的情况下保证一定程度的安全性,特别是在单一线程更新 volatile 变量,且操作不依赖于变量当前值的情况下。然而,对于更复杂的多线程同步需求,通常还需要结合其他的同步机制。
文章标题:volatile 关键字
本文作者:zhu8fei
发布时间:2024-08-14, 10:40:10
最后更新:2024-08-15, 09:37:02
原始链接:http://www.zhu8fei.com/2024/08/14/volatile.html版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 zhu8fei@163.com