【问题标题】:Is reference update thread safe?参考更新线程安全吗?
【发布时间】:2012-10-23 14:29:23
【问题描述】:
public class Test{
   private MyObj myobj = new MyObj(); //it is not volatile


   public class Updater extends Thred{
      myobje = getNewObjFromDb() ; //not am setting new object
   }

   public MyObj getData(){
    //getting stale date is fine for 
    return myobj;
   }


}

定期更新更新myobj
其他类使用 getData
获取数据 在不使用 volatile 关键字的情况下,此代码线程安全吗?
我想是的。有人可以确认吗?

【问题讨论】:

标签: java multithreading thread-safety


【解决方案1】:

,这不是线程安全的。 (是什么让你认为它是?)

如果您在一个线程中更新变量并从另一个线程读取它,则必须在写入和后续读取之间建立happens-before relationship

简而言之,这基本上意味着同时读取和写入synchronized(在同一监视器上),或引用volatile

否则,无法保证读取线程会看到更新 - 它甚至不会像“嗯,它会看到旧值或新值”那么简单。您的阅读器线程可能会看到一些非常奇怪的行为以及随之而来的数据损坏。看看lack of synchronization can cause infinite loops,例如(那篇文章的cmets,尤其是Brian Goetz',非常值得一读):

故事的寓意:每当跨线程共享可变数据时,如果您没有正确使用同步(这意味着使用公共锁来保护对共享变量的每次访问,无论是读取还是写入),您的程序都会被破坏,并且以你可能无法列举的方式破坏。

【讨论】:

  • 如果读取线程得到陈旧数据就可以了。我还需要使用volatile吗。
  • 是的。如果您不使用适当的同步,那么您的代码的行为是未定义的。
【解决方案2】:

不,不是。

如果没有volatile,从其他线程调用getData() 可能会返回一个过时的缓存值。
volatile 强制一个线程的分配立即在所有其他线程上可见。

请注意,如果对象本身不是不可变的,您可能会遇到其他问题。

【讨论】:

  • 在我的情况下获取陈旧数据很好。但不想得到任何异常。现在在这种情况下它是线程安全的吗?
  • 您预计会出现哪种异常? myobj 只能是 null 或另一个值,它们都是 MyObj 的良好返回值。你需要处理一个空返回,但是,从这个意义上说,它是“线程安全的”。
  • ConcurrentModifications 或一些并发异常。可能会在更新程序更新引用时尝试读取数据。如果调用者 thred 过时,我可以
  • @Sebastian 我不太确定。如果引用是非易失性的,JLS 是否保证它会自动更新?使用非易失性long 值,未同步的阅读器可以看到“撕裂”,其中一个词来自旧值,一个词来自新值。而且我几乎可以肯定 JLS 将保证在这种情况下您将获得“旧值或新值”。可能会导致任何行为,包括致命的热点错误。
  • @Sebastian 根据我回答中链接中的 Brian Goetz 所说:“除非您精通并发,否则在没有同步的情况下您对“可能出错”的直觉(即使是只读的)路径)通常是不完整的,并且推理事情发生的顺序非常棘手,以至于你几乎肯定会出错。”
【解决方案3】:

可能获得过时的参考资料。您可能不会得到无效的参考。 你得到的引用是对变量指向或指向或将指向的对象的引用的值。

请注意,no 保证引用可能有多少陈旧,但它仍然是对 some 对象和该对象的引用em>仍然存在。换句话说,写一个引用是原子的(写期间什么都不会发生)但不是同步的(它受指令重新排序、线程本地缓存等) .

如果您将引用声明为volatile,您将围绕变量创建一个同步点。简单来说,这意味着访问线程的所有缓存都被刷新(写入被写入,读取被遗忘)。

唯一没有原子读/写的类型是 longdouble,因为它们在 32 位机器上大于 32 位。

【讨论】:

  • 所以如果我对陈旧的数据没问题(就像我的情况一样),不使用 volatile 可以吗?
【解决方案4】:

如果MyObj 是不可变的(所有字段都是最终的),则不需要 volatile。

【讨论】:

    【解决方案5】:

    这类代码的大问题是延迟初始化。如果没有 volatilesynchronized 关键字,您可以为尚未完全初始化的 myobj 分配一个新值。 Java 内存模型允许在对象构造函数返回后执行部分对象构造。这种内存操作的重新排序是内存屏障在多线程情况下如此重要的原因。

    没有内存屏障限制,没有发生之前的保证,因此您不知道MyObj 是否已完全构建。这意味着另一个线程可能正在使用部分初始化的对象并产生意外结果。

    以下是关于构造函数同步的更多细节:

    Constructor synchronization in Java

    【讨论】:

      【解决方案6】:

      Volatile 适用于布尔变量,但不适用于引用。 Myobj 似乎表现得像一个缓存对象,它可以与 AtomicReference 一起使用。由于您的代码从数据库中提取值,因此我将让代码保持原样并向其添加 AtomicReference。

      import java.util.concurrent.atomic.AtomicReference;
      
          public class AtomicReferenceTest {
              private AtomicReference<MyObj> myobj = new AtomicReference<MyObj>();
      
              public class Updater extends Thread {
      
                  public void run() {
                      MyObj newMyobj = getNewObjFromDb();
                      updateMyObj(newMyobj);
                  }
      
                  public void updateMyObj(MyObj newMyobj) {
                      myobj.compareAndSet(myobj.get(), newMyobj);
                  }
      
                   }
              public MyObj getData() {
                   return myobj.get();
              }
          }
      
          class MyObj {
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-15
        • 1970-01-01
        • 2011-07-04
        • 2014-04-26
        相关资源
        最近更新 更多