在对象上同步并更改引用

Synchronizing on an object and changing the reference

假设我有一个对象如下:

Map<String, String> m = new HashMap<>();

然后我按如下方式同步此对象并更改其引用:

synchronize(m){
    m = new HashMap<>();
}

使用这段代码,m 上的锁会发生什么变化?更新由 m 表示的新对象是否仍然安全?或者锁本质上是在旧对象上?

要安全地更改对对象的引用,您可以:

  1. 使用AtomicReference

    AtomicReference<Map<String, String>>
    
  2. 在包含此地图的对象上使用 synchronized,或者在其他一些锁定对象上使用更好。

    class A {
        private final Object lock = new Object();
        private Map<String, String> m = new HashMap<>();
    
        public void changeMap() {
            synchronized(lock){
                m = new HashMap<>();
            }
        }
    }
    
  3. 至少加上volatile

    private volatile Map<String, String> m = new HashMap<>();
    

另请参阅有关此主题的其他答案

  1. Is reference update thread safe?
  2. In Java, is it safe to change a reference to a HashMap read concurrently

您的方法不安全。您需要在所有协调线程中使用 same 锁来保护某些资源(在本例中为映射 m),但正如您直觉所理解的那样,这在这里失败了,因为对象 m不断变化。

具体来说,一旦你在临界区内写入对 m 的新引用,另一个线程就可以进入临界区(因为它们获得了 new[=24 上的锁=] Map,而不是另一个线程持有的旧地图),并访问新的 部分构建的 地图。

另请参阅安全发布

锁在对象上,而不是在变量上。

当线程尝试进入同步块时,它会计算同步关键字后括号中的表达式,以确定要获取锁定的对象。

如果覆盖指向新对象的引用,则下一个尝试进入同步块的线程将获取新对象上的锁,因此可能出现两个线程正在执行代码的情况同一个对象上的同一个同步块(当另一个线程开始执行该块时,获取旧对象锁的那个可能不会完成)。

要使互斥起作用,您需要线程共享同一个锁,不能让线程换出锁对象。最好有一个专用对象用作锁,将其设置为 final 以确保没有任何更改,如下所示:

private final Object lock = new Object();

这样一来,由于锁对象不用于任何其他用途,因此不会有去更改它的诱惑。

内存可见性在这里似乎不相关。在推理交换锁如何产生问题时不需要考虑可见性,添加代码让锁对象以可见的方式改变无助于解决问题,因为解决方案是避免改变完全锁定对象。

来自JLS 17.1

The synchronized statement (§14.19) computes a reference to an object; it then attempts to perform a lock action on that object's monitor and does not proceed further until the lock action has successfully completed. After the lock action has been performed, the body of the synchronized statement is executed. If execution of the body is ever completed, either normally or abruptly, an unlock action is automatically performed on that same monitor.

现在是问题。

m 上的锁发生了什么变化?

没有。这有点令人困惑。实际上,线程在尝试获取锁时正持有 m 引用的对象 上的锁。在同步块中分配给 m 不会自动 "switch" 执行线程持有的锁。

更新m代表的新对象还安全吗?

不安全。对 m 的写入未在同一锁上同步。

或者锁本质上是在旧对象上?