【问题标题】:singleton with volatile in javajava中带有volatile的单例
【发布时间】:2013-02-25 22:40:42
【问题描述】:
class MyClass
{
      private static volatile Resource resource;

      public static Resource getInstance()
      {
            if(resource == null)
                  resource = new Resource();
            return resource;
      }
 }

如果您使用易失性,安全发布发生(即,一旦引用对另一个线程可见,数据也可用),我的怀疑是在实践中根据 java 并发性。那么我可以在这里使用它吗?但如果它是正确的,那么假设 thread1 现在检查“资源”并且它为空,因此它开始创建对象。当 thread1 创建对象时,另一个线程即 thread2 来并开始检查“resource”的值,thread2 发现它为 null(假设创建“resource”对象需要相当长的时间,并且由于 thread1 尚未完成创建,所以安全发布没有发生,因此 thread2 不可用)那么它也会开始创建对象吗?如果是,则类不变量中断。我对么?请在这里帮助我理解 volatile 的这种特殊用法。

【问题讨论】:

  • 你不会在单例中使用volatile根据定义,单例中的私有实例不会改变,这意味着线程不存在缓存旧值的危险。这忽略了您当前具有非线程安全实现,因为您的 getInstance 未同步。使用 enum 在 Java 中创建单例。
  • 对于几乎所有用途,您不妨只使用初始化表达式,因为类加载是延迟完成的。如果您出于其他原因使用该类,可能没有使用此实例(听起来很糟糕),那么包含静态的嵌套类将很好地完成这项工作。
  • 如果您绝对必须使用单例,请考虑:en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom

标签: java concurrency


【解决方案1】:

你是对的,多个线程可以尝试创建一个 Resource 对象。 Volatile 只是保证如果一个线程更新了引用,所有其他线程都会看到新的引用,而不是一些缓存的引用。这更慢,但更安全。

如果您只需要一个延迟加载的资源,则需要执行以下操作:

class MyClass
{
      private static volatile Resource resource;
      private static final Object LOCK = new Object();

      public static Resource getInstance()
      {
            if(resource == null) { 
                synchronized(LOCK) { // Add a synch block
                    if(resource == null) { // verify some other synch block didn't
                                           // write a resource yet...
                        resource = new Resource();
                    }
                }
            }
            return resource;
      }
 }

【讨论】:

  • @corsiKa 感谢您的及时回复。我可以这样说吗“由于 volatile 仅通过符合发生之前的习惯用法来解决可见性问题,但在这种情况下,我们需要可见性和原子性,即检查条件并初始化对象(资源)。所以要么是同步的,要么是静态的初始化器是必须的”。如果我错了,请纠正我。
  • @user2109070 我会说这是一个足够的总结。如果我正在对代码进行代码审查,那将是一个合理的陈述。
  • 或者您可以使用enum 并避免所有这些。 stackoverflow.com/questions/70689/…
  • @ssedano 内部锁可能更容易。如果您不需要支持的方法(如 tryLock 或多个条件),您可能应该使用同步。
【解决方案2】:

volatile 解决了一个问题,即可见性问题。如果您正在写入一个声明为 volatile 的变量,那么该值将立即对其他线程可见。众所周知,我们在 os L1、L2、L3 中有不同级别的缓存,如果我们在一个线程中写入变量,则不能保证对其他线程可见,因此如果我们使用 volatile,它会写入直接内存并且是可见的给别人。但是 volatile 并不能解决 原子性 的问题,即 int a; a++; 不安全。因为有三个机器指令与之关联。

【讨论】:

    【解决方案3】:

    我知道您不是在询问更好的解决方案,但如果您正在寻找一个惰性单例解决方案,这绝对值得。

    使用私有静态类来加载单例。在调用之前不会加载该类,因此在该类加载之前不会加载引用。通过实现加载类是线程安全的,并且您也产生非常少的开销(如果您正在执行重复的易失性加载[这可能仍然很便宜],此解决方案在初始构造后总是正常加载)。

    class MyClass {
        public static Resource getInstance() {
            return ResourceLoader.RESOURCE;
        }
    
        private static final class ResourceLoader {
            private static final Resource RESOURCE = new Resource();
        }
    }
    

    【讨论】:

      【解决方案4】:

      我认为,您应该在getInstance 定义之前使用syncronized 关键字。

      为了获得更好的性能,您可以使用双重检查锁定模式:

      【讨论】:

      • 这也需要所有读取同步。这将是非常不可取的。
      【解决方案5】:

      当应用于字段时,Java volatile 保证:

      1. (在所有 Java 版本中)读取有全局排序 并写入 volatile 变量。这意味着每个线程 访问 volatile 字段将在之前读取其当前值 继续,而不是(可能)使用缓存值。 (然而, 无法保证 volatile 读取的相对顺序 并以常规读写方式进行写入,这意味着它是 通常不是有用的线程构造。)

      2. (在 Java 5 或更高版本中)易失性读写建立一个 发生之前的关系,就像获取和释放一个 互斥体。

      更多info.

      【讨论】:

      • 但是你没有回答我的问题。 :)
      【解决方案6】:

      您是对的,在这种情况下,由于您描述的种族,资源可能会被构造两次。如果您想在 Java 5+ 中实现单例(没有显式锁定),请使用What is an efficient way to implement a singleton pattern in Java? 的答案中描述的枚举单例。

      【讨论】:

        【解决方案7】:

        volatile 关键字保证对该变量的读写是原子的。

        根据tutorial

        Reads and writes are atomic for all variables declared volatile
        

        使用 volatile 变量可降低内存一致性的风险 错误,因为对 volatile 变量的任何写入都会建立 与后续读取相同的发生之前的关系 多变的。这意味着对 volatile 变量的更改总是 对其他线程可见。更重要的是,这也意味着当一个 线程读取一个 volatile 变量,它看到的不仅仅是最新的变化 易变的,还有导致代码的副作用 变化。

        【讨论】:

          【解决方案8】:

          首先,以这种方式拥有一个单例,您实际上是在创建一个全局对象,这是一种不好的做法。我认为您使用 Enums 代替。

          【讨论】:

            【解决方案9】:

            这是我将 volatile 和 synchronized 一起添加的建议。

            注意:我们仍然需要仔细检查。

            public class MySingleton {
                private static volatile MySingleton instance;
                private MySingleton() {}
            
                synchronized private static void newInstance() {
                    if(instance == null) {
                        instance = new MySingleton();
                    }
                }
            
                public static MySingleton get() {
                    if(instance == null) {
                        newInstance();
                    }
                    return instance;
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-07-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-05-31
              • 2010-11-06
              • 2019-10-10
              相关资源
              最近更新 更多