不,这不会按您的预期工作。我经常看到这种关于 volatile 的特殊观点,值得研究一下。
成员引用和其中包含 volatile 关键字的类型在两个操作之外不会获得任何与并发相关的特殊属性:
- 易失性读取(例如访问字段)
- 易失性写入(例如,分配给字段)
就是这样。并且这些特殊操作 (17.4.2) 仅适用于对成员字段本身的操作,不适用于任何可能从存储对象调用的方法。
例如:
private volatile List<Foo> foos = null;
private void assign() {
foos = new ArrayList<>(); // This is a volatile write
// This mutation is not handled any differently than any other list.
// It is not special simply because the list referenced happens
// to be assigned to a volatile. If another thread accessed the
// field 'foos' it may see an inconsistent state (null, empty list,
// or possible worse) because this is not thread-safe.
foos.add(new Foo());
}
把上面的突变想象成这样:
List<Foo> local = foos; // This is a volatile read, and is specially handled
local.add(new Foo()); // There is no special handling of this mutation
那么,volatile 的意义何在?好吧,如前所述,唯一区别对待的两个操作是分配给 volatile 字段和访问 volatile 字段。易失性写入与其他线程中对该字段的任何读取(访问)创建所谓的“发生前”关系。简而言之,如果线程 A 对字段执行易失性写入,则线程 B 访问该字段,它将看到它的先前状态或新状态。与上面的示例相比,线程 B 可以看到对象处于不一致的状态,而上面的示例中另一个线程可以看到 foos 处于不一致的状态,因此不会存在中间状态。您可以利用它来发挥自己的优势:
private volatile List<Bar> bars = null;
private void assign() {
List<Bar> local = new ArrayList<>(); // local copy
local.add(new Bar());
bars = local; // Volatile write
}
看到这里的不同之处在于我们在本地创建了一个对象,将其完全初始化,然后将其分配给 volatile 成员。现在,访问字段“bars”的任何线程都将看到 null(先前的状态)或带有一个元素的完全构造的列表。这当然只适用于您不尝试就地改变列表,而且重要的是,只要您调用访问器方法时列表不会自行改变。
另外,只需使用 ConcurrentHashMap。