读取和写入变量引用

时间:2017-08-19 17:12:31

标签: java multithreading

我读过Oracle's documentation,其中指出(在其他一些事情中):

  

读取和写入对于引用变量是原子的

这意味着,假设我理解正确,下面的代码是线程安全的,不需要volatilesynchronized或使用Lock类,因为{的实际赋值{1}}到otherHashlist是原子的,从hashlisthashlist的分配也是原子的。

tempHashlist

此外,永远不会以public class SomeClass { private HashMap<Integer, ArrayList<MyObject>> hashlist = null; // ...Thread/Runnable logic to periodically call set()... private void set() { HashMap<Integer, ArrayList<MyObject>> otherHashlist = new HashMap<Integer, ArrayList<MyObject>>(); // ...populate otherHashlist... hashlist = otherHashlist; } public ArrayList<MyObject> get(int i) { HashMap<Integer, ArrayList<MyObject>> tempHashlist = hashlist; return new ArrayList<MyObject>(tempHashlist.get(i)); } } hashlist之外的任何方式访问get()set()也无法由班级以外的任何人直接或间接调用。 set()返回的ArrayList是get(),因此修改ArrayList(set(),remove()等)的操作不会影响new中的原始ArrayList。我还没有在hashlist上定义任何setter方法,其所有成员变量都是MyObjectprivate final intprivate final long

那么,我的问题是:这段代码实际上是线程安全吗?或者是否有一些假设我正在制作/角度我错过了会使这不安全?

2 个答案:

答案 0 :(得分:1)

取决于预期的行为......

如果每个线程都有自己的SomeClass实例 - 它是线程安全的。因此,我们假设多个线程具有相同的实例:

现在说两个线程同时调用set,并且在其中一个线程执行赋值后立即执行:hashlist = otherHashlist;另一个线程正在执行完全相同的操作(可能具有不同的内容)。 这意味着对get(i)的两次连续调用可能会返回不同的结果,这意味着不保持一致性。此外,由于线程具有本地缓存​​,因此某些线程可能会看到hashlist的较旧(陈旧)副本。

这是一种公认​​的行为吗?如果是(IMO有点奇怪),那么你很好。

我强烈推荐Brian Goetz阅读:Java Concurrency in Practice,围绕章节:“3.5。安全发布”

答案 1 :(得分:1)

这不是线程安全的,因为尽管设置字段是安全的并且不会导致重叠更新,但其他线程实际上可能不会将这些更改可见。也就是说,即使Thread1hashlist设置为某个内容,Thread2也可能看不到这种更改。这是因为允许JVM优化对hashlistThread2的访问,比如将引用复制到寄存器中,因此不需要多次执行getfield。在这种情况下,当Thread1更改hashlist时,Thread2无法看到它,因为它不再使用该字段,它正在使用它的本地副本,它认为< em>是字段。 This question显示了在这种情况下会发生什么。

最简单的方法是标记hashlist volatile,这意味着其他人实际上可以看到一个帖子中的更改。