【问题标题】:Synchronizing on an object and changing the reference在对象上同步并更改引用
【发布时间】:2017-03-31 10:47:44
【问题描述】:

假设我有一个对象如下:

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

然后我在这个对象上同步如下并更改它的引用:

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

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

【问题讨论】:

  • 这不安全 - 你可能会在另外 2 个线程之间发生竞争,其中一个线程已经阻塞等待旧的m,另一个线程获得了新的m 的锁定。出于这个原因,锁对象通常与保存数据的对象分开。

标签: java multithreading concurrency synchronized synchronized-block


【解决方案1】:

来自JLS 17.1

同步语句(第 14.19 节)计算对对象的引用; 然后它尝试在该对象的监视器上执行锁定操作并 在锁定操作成功之前不会继续进行 完全的。锁定动作执行后,主体 执行同步语句。如果身体的执行是永远 正常或突然完成,解锁动作是 在同一台监视器上自动执行。

现在是问题。

m 上的锁会怎样?

什么都没有。这有点令人困惑。实际上,线程在尝试获取锁时正在持有对象引用 m 的锁。在同步块中对m 的赋值不会自动“切换”正在执行的线程所持有的锁。

更新 m 表示的新对象是否仍然安全?

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

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

是的

【讨论】:

    【解决方案2】:

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

    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

    【讨论】:

    • “在包含此地图的对象上使用同步”---我认为这不是最好的建议。
    • "至少添加 volatile" --- 如果你有synchronized,为什么还需要它?
    • @zerkms 当然这不是最好的建议,这就是为什么我写了 better on some other lock object 并提供了示例。当然,如果您有synchronized,则无需添加volatile。至少有 3 种不同的方法可以使引用线程安全,无需混合使用。我的回答不清楚吗?
    • 目前尚不清楚(不清楚)这些建议是否适用于 OP 所拥有的内容(尤其是#3)或暗示应该从头开始重写。否则 - 一个很好的答案:-)
    【解决方案3】:

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

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

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

    为了使互斥起作用,您需要线程共享同一个锁,您不能让线程交换锁对象。最好有一个专用对象用作锁,使其成为最终对象,以确保没有任何改变,如下所示:

    private final Object lock = new Object();
    

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

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

    【讨论】:

      【解决方案4】:

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

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

      另请参阅安全发布

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-26
        • 1970-01-01
        • 2016-01-29
        相关资源
        最近更新 更多