假设我有一个如下对象:
Map<String, String> m = new HashMap<>();
然后我按如下方式对此对象进行同步并更改其引用:
synchronize(m){
m = new HashMap<>();
}
使用此代码,m上的锁会发生什么?更新m代表的新对象是否仍然安全?或者锁是否基本上在旧对象上?
答案 0 :(得分:3)
要安全地更改对象的引用,您可以:
AtomicReference<Map<String, String>>
对包含此地图的对象使用synchronized
或更好地使用其他锁定对象。
class A {
private final Object lock = new Object();
private Map<String, String> m = new HashMap<>();
public void changeMap() {
synchronized(lock){
m = new HashMap<>();
}
}
}
至少添加volatile
private volatile Map<String, String> m = new HashMap<>();
另见本主题的其他答案
答案 1 :(得分:3)
锁定在对象上,而不在变量上。
当一个线程试图进入一个synchronized块时,它会在synchronized关键字后面的parens中计算表达式,以确定获取锁定的对象。
如果覆盖引用以指向新对象,则尝试进入synchronized块的下一个线程将获取新对象的锁定,因此可能是两个线程在同一个同步中执行代码的情况阻止同一个对象(当另一个线程开始执行该块时,可能无法完成对旧对象获取锁定的对象)。
要使互斥工作,您需要线程共享相同的锁,您不能让线程交换锁对象。最好有一个专门的对象用作锁定,最后确保没有任何改变,如下所示:
private final Object lock = new Object();
这样,由于锁定对象不用于其他任何东西,因此没有改变它的诱惑。
内存可见性似乎与此无关。在推断锁定如何交换产生问题时,您不需要考虑可见性,并且添加代码以使锁定对象以可见方式更改无助于解决问题,因为解决方案是避免更改完全锁定对象。
答案 2 :(得分:1)
来自JLS 17.1:
synchronized语句(第14.19节)计算对象的引用; 然后它尝试在该对象的监视器上执行锁定操作 在锁定操作成功之前不会继续进行 完成。锁定动作执行后,身体的 执行synchronized语句。如果身体的执行永远 完成,无论是正常还是突然,解锁动作都是 在同一台显示器上自动执行。
现在问题。
m上的锁会发生什么?
无。这有点令人困惑。实际上,线程在尝试获取锁时,在 m
引用的对象上持有锁。在synchronized块中对m
的赋值不会自动“切换”正在执行的线程所持有的锁。
更新由m?
表示的新对象是否仍然安全这不安全。对m
的写入不会在同一个锁上同步。
或者锁定主要是在旧对象上?
是
答案 3 :(得分:0)
你的方法并不安全。您需要在所有协调线程中使用相同的锁来保护某些资源(在这种情况下为地图m
),但是直观地理解这在此处失败,因为对象{{1}不断变化。
具体来说,一旦你在关键部分内写了m
的新引用,另一个线程就可以进入关键部分了(因为它们锁定了 new {{1而不是其他线程持有的旧版本),并访问新的部分构造的映射。
另请参阅安全发布。