【问题标题】:Guarantees of AtomicReference原子引用的保证
【发布时间】:2012-11-01 23:33:06
【问题描述】:

AtomicReference 的语义是什么?
如果我这样做:

AtomicReference<CustomObject> ref = new AtomicReference<CustomObject>();

然后我做:

public void someMethod(){  
 //do something  

 ref.set(loadNewData());  

}  

private final Sempahore updatePermit = new Sempahore(1);  

private CustomObject loadNewData(){  
     CustomObject obj = null;  
     if (updatePermit.tryAcquire()) {  
        obj = ...; //do the loading and create Object  
        updatePermit.release();  
    } else {  
        //update already running, wait  
        updatePermit.acquire();  
        //release the permit immediately  
        updatePermit.release();  
        obj = ref.get(); //????
    }  
    return obj;    
}    

是否有保证在线obj = ref.get(); //???? get 将返回CustomObject新鲜版本?
这与post的assylias的回答有关:

【问题讨论】:

    标签: java multithreading concurrency java.util.concurrent


    【解决方案1】:

    实际上,您的代码存在竞争条件,可能会导致新数据丢失:

    1. 第一个调用者进入 loadNewData 并开始加载新数据(有信号量)
    2. 第二个调用者无法获取信号量并在获取时等待
    3. 第一个调用者完成加载并释放信号量(但尚未返回新数据)
    4. 第二个调用者获取信号量,调用ref.get() 并获取数据
    5. 第一个调用者返回传递给ref.set()的新数据
    6. 第二个调用者返回数据,当传递给ref.set()时覆盖新数据

    由于someMethod()总是加载新数据,而您总是希望调用者在新数据加载时等待,所有这些额外的东西都是无用的。只需在整个块周围使用一个简单的同步块(或锁)并放弃原子引用。根据链接的帖子,您似乎只想执行一次此操作,因此请使用初始化标志。

    private boolean _initialized;
    
    public synchronized void loadLatestData() {
      if(!_initialized) {
          // load latest data ...
          _initilized = true;
      }
    }
    

    【讨论】:

    • 解决此问题的方法是将ref.set 移动到loadNewData 中的信号量release 之前。
    • @IanRoberts - 是的,或者干脆放弃所有废话并使用简单的锁。
    • @jtahlborn 我认为一个简单的锁不能解决链接帖子中详述的原始问题。我同意比赛条件。如果 2 个线程 T1 和 T2 调用loadNewData,T1 应该加载数据,T2 应该等待 T1 加载数据并在 T1 加载完成后退出该方法。
    • @assylias - 我从未真正看过链接的帖子。尽管如此,一个简单的布尔“初始化”标志将解决“只做一次”的问题。会更新。
    • @jtahlborn 这实际上不是“只做一次”的问题。请参阅我上面的评论(链接帖子中的原始问题不清楚)。
    【解决方案2】:

    是的。
    AtomicReference 保证发布。

    但是,不能保证其他线程不会在毫秒后设置它。

    请注意,如果您不调用 compareAndSet()AtomicReference 并不比 volatile 字段好。

    【讨论】:

    • 如果线程A 执行updatePermit.release(); 是否保证线程B 能够“看到”从线程A 返回的值? IE。 ref 会在之前更新 B 在释放信号量后尝试做get 吗?
    • 但我不需要compareAndSet
    • @Jim:那你不妨改用volatile
    【解决方案3】:

    阅读JavaDoc of java.util.concurrent.atomic

    • get 具有读取volatile 变量的记忆效应。

    • set 具有写入(分配)volatile 变量的记忆效应。

    所以保证是:get() 总是返回传递给set()的最后一个值。在此期间您做什么、使用哪种锁等都无关紧要。修改volatile 变量(您实际上是这样做的)保证对读取该值的所有其他线程可见。

    【讨论】:

    • 如果线程A 执行updatePermit.release(); 是否可以保证线程B 将能够“看到”从线程A 返回的值? IE。 ref 会在之前更新 B 在释放信号量后尝试做get 吗?
    • @Jim:信号量在这里无关紧要,请阅读volatile 变量保证。 Somephores 和同步只与 normal 变量有关。
    • 那么什么时候会更喜欢AtomicReference 而不是volatile
    • @Jim:如果您只使用get()set(),除了可读性之外没有任何实际好处。当您使用 CAS 操作和其他高性能非阻塞方法(如 incrementAndGet())时,atomic* 非常有用。
    【解决方案4】:

    原子变量(包括AtomicReference)具有与volatile 字段相同的属性,因为它们在线程之间建立了happens-before 关系。如果一个线程 A 将一个值写入 volatile 字段(或原子变量),而另一个线程 B 从同一个变量读取,那么您不一定能保证 B会或不会看到A的变化,但你可以保证

    • IF B 看到 A 写入的值
    • THEN B 还将看到 A其他 字段(易失性和非易失性)的任何写入结果在它写 volatile 字段之前做了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-25
      • 2021-07-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多