我读过Oracle's documentation,其中指出(在其他一些事情中):
读取和写入对于引用变量是原子的
这意味着,假设我理解正确,下面的代码是线程安全的,不需要volatile
,synchronized
或使用Lock
类,因为{的实际赋值{1}}到otherHashlist
是原子的,从hashlist
到hashlist
的分配也是原子的。
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方法,其所有成员变量都是MyObject
,private final int
或private final long
。
那么,我的问题是:这段代码实际上是线程安全吗?或者是否有一些假设我正在制作/角度我错过了会使这不安全?
答案 0 :(得分:1)
取决于预期的行为......
如果每个线程都有自己的SomeClass
实例 - 它是线程安全的。因此,我们假设多个线程具有相同的实例:
现在说两个线程同时调用set
,并且在其中一个线程执行赋值后立即执行:hashlist = otherHashlist;
另一个线程正在执行完全相同的操作(可能具有不同的内容)。
这意味着对get(i)
的两次连续调用可能会返回不同的结果,这意味着不保持一致性。此外,由于线程具有本地缓存,因此某些线程可能会看到hashlist
的较旧(陈旧)副本。
这是一种公认的行为吗?如果是(IMO有点奇怪),那么你很好。
我强烈推荐Brian Goetz阅读:Java Concurrency in Practice,围绕章节:“3.5。安全发布”
答案 1 :(得分:1)
这不是线程安全的,因为尽管设置字段是安全的并且不会导致重叠更新,但其他线程实际上可能不会将这些更改可见。也就是说,即使Thread1
将hashlist
设置为某个内容,Thread2
也可能看不到这种更改。这是因为允许JVM优化对hashlist
中Thread2
的访问,比如将引用复制到寄存器中,因此不需要多次执行getfield
。在这种情况下,当Thread1
更改hashlist
时,Thread2
无法看到它,因为它不再使用该字段,它正在使用它的本地副本,它认为< em>是字段。 This question显示了在这种情况下会发生什么。
最简单的方法是标记hashlist
volatile
,这意味着其他人实际上可以看到一个帖子中的更改。