title: 关于 volatile 的一些小问题
date: 2021/03/01 13:46
- ArrayList 为啥线程不安全
在 add 方法中的 elementData[size++] = e
存在线程不安全的风险。
elementData 与 size 都是全局变量,但没有进行同步处理,elementData是共享的线程不安全的可变数据。
- 使用 volatile 修饰 List 是线程安全吗?
很明显,如果你有这个疑问那么肯定你对上面的那个问题还不了解,ArrayList 具有线程安全问题的原因是 add 方法对 size 和 elementData 的操作并不具有原子性。
而使用 volatile 修饰 List 只是让其他线程对这个变量具有了可见性,对他的操作还是不具有原子性,所以还是线程不安全的。
- volatile 与 AtomicReference
volatile 修饰的变量具有可见性、有序性,但是不具有原子性,多个线程对变量的操作可能会出现问题。
AtomicReference 保证了包装的对象的引用修改时的原子性。
- 为什么 AtomicReferenceFieldUpdater(字段更新器)要配合 volatile 使用
因为修改之后其他线程要立刻可见啊,AtomicReferenceFieldUpdater 只是原子更新,防止更新的时候失败。
附录:volatile
定义
volatile 是 Java 提供的一种稍弱的同步机制,用于确保将变量的更新操作通知到其他线程(虽然我不知道他是怎么通知的)。当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。修改 volatile 变量时,会将数据立即写回内存,然后导致缓存了该数据的其他cpu的缓存无效,这样其他线程在使用该数据的时候必须从内存重新读取数据到缓存,保证了可见性。
注意:将可变对象字段标记为 volatile 意味着对象引用是 volatile,但是对象本身不是,其他线程可能看不到对象状态的更新。举个例子吧:
volatile List<User> userList = new ArrayList<>();
public static void main(String[] args) {
User u = new User("zs");
// 修改了 ArrayList 内部的 elementData 属性,不会通知其他线程(虽然我不知道他是怎么通知的)
userList.add(u);
// 修改了 ArrayList 内部的 elementData 的第一个元素的 name 属性,不会通知其他线程(虽然我不知道他是怎么通知的)
u.setName = "ls";
// 修改了 ArrayList 内部的 elementData 的第一个元素的引用,不会通知其他线程(虽然我不知道他是怎么通知的)
userList.add(0, new User("ww"));
// 修改了 userList 的引用,会通知其他线程
userList = new ArrayList<>();
// 也就是说虽然 userList 使用了 volatile 修饰,但也只是保证了他自己的可见性,至于其中的 elementData 属性还有 elementData 属性中的 User 对象都是不保证可见性的,其他线程如果执行 u.setAge
}
// 注:以上只是理论上的逻辑,使用代码无法测试出来,好像是因为不同版本不同厂商的虚拟机,执行策略都不一样 0.0.. [volatile修饰数组或引用对象的问题](https://www.jianshu.com/p/10ef84dc5eef)
结论
volatile 只保证修饰的变量的可见性,对其内容不保证可见性。