java中带volatile的单例

java中带volatile的单例,java,concurrency,Java,Concurrency,在这里,我的疑问是,如果您使用易失性的、安全的发布(即,当引用对另一个线程可见时,数据也可用),那么实际上是java并发的。我能在这里用吗?但是如果它是正确的,那么假设thread1现在检查“resource”,并且它为null,因此它开始创建对象。当thread1正在创建对象时,另一个线程(即thread2)出现并开始检查“resource”的值,thread2发现该值为null(假设创建“resource”对象需要相当长的时间,并且thread1尚未完成创建,因此安全发布尚未发生,因此thr

在这里,我的疑问是,如果您使用易失性的、安全的发布(即,当引用对另一个线程可见时,数据也可用),那么实际上是java并发的。我能在这里用吗?但是如果它是正确的,那么假设thread1现在检查“resource”,并且它为null,因此它开始创建对象。当thread1正在创建对象时,另一个线程(即thread2)出现并开始检查“resource”的值,thread2发现该值为null(假设创建“resource”对象需要相当长的时间,并且thread1尚未完成创建,因此安全发布尚未发生,因此thread2不可用)然后它还会开始创建对象吗?如果是,则类不变中断。我说得对吗?请帮助我理解volatile在这里的特殊用法。

没错,多个线程可以尝试创建一个资源对象。Volatile只保证如果一个线程更新引用,那么所有其他线程将看到新引用,而不是一些缓存的引用。这是缓慢的,但更安全

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

class MyClass
{
      private static volatile Resource resource;

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

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

根据

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


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

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


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

  • (在所有版本的Java中)读操作都有全局排序 并写入一个可变变量。这意味着每个线程 访问易失性字段之前,将读取其当前值 继续,而不是(可能)使用缓存值。(但是, 不保证易失性读取的相对顺序 并定期读写,这意味着 通常不是有用的线程构造。)

  • (在Java5或更高版本中)易失性读写建立了 发生在恋爱之前,就像获得和释放 互斥


  • 更多。

    您是正确的,在这种情况下,由于您描述的竞争,资源可能会构造两次。如果您想在Java 5+中实现一个单例(无显式锁定),请使用enum单例,如的回答中所述。

    我知道您并没有询问更好的解决方案,但如果您正在寻找一个懒惰的单例解决方案,这绝对值得

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


    首先,以这种方式创建一个单例,实际上是在创建一个全局对象,这是一种不好的做法。我想您应该改用枚举。

    volatile解决了一个问题,即可见性问题。如果您正在写入一个声明为volatile的变量,那么其他线程将立即看到该值。我们都知道,在操作系统L1、L2、L3中有不同级别的缓存,如果我们在一个线程中写入一个变量,它就不能保证对另一个线程可见,因此如果我们使用易失性,它会写入直接内存,对其他线程可见。但是volatile不能解决原子性的问题,即
    inta;a++不安全。因为有三条机器指令与之关联。

    我建议将volatile和synchronized添加到一起

    注意:我们仍然需要做双重检查

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

    您不会在单例中使用
    volatile
    。根据定义,单例中的私有实例不会改变,这意味着线程不会缓存旧值。这将忽略您当前的非线程安全实现,因为您的
    getInstance
    未同步。使用
    enum
    在Java中创建单例。对于几乎所有的使用,您都可以使用初始化表达式,因为类加载是延迟完成的。如果您使用该类的原因可能是不使用此实例(听起来不好),那么嵌套类包含静态将很好地完成这项工作。如果您绝对需要使用单体,请考虑这一点:这也需要所有读取的同步。这是非常不受欢迎的。我宁愿使用lock objects@corsiKa,谢谢你的及时回复。我可以这样说吗“因为volatile只通过符合习惯用法之前发生的情况来解决可见性问题,但在这种情况下,我们需要可见性和原子性,即检查条件并初始化对象(资源)。因此需要同步或静态初始值设定项”。如果我错了,请纠正我。@user2109070我认为这是一个足够的总结。如果我正在对代码进行代码审查,那么这将是一个合理的声明。或者您可以使用
    enum
    并避免所有这些@ssedano使用内部锁可能更容易。如果您不需要支持的方法(如tryLock或多个条件),您可能应该使用synchronized。但您没有回答我的问题。:)
    Reads and writes are atomic for all variables declared volatile
    
    class MyClass {
        public static Resource getInstance() {
            return ResourceLoader.RESOURCE;
        }
    
        private static final class ResourceLoader {
            private static final Resource RESOURCE = new Resource();
        }
    }
    
    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;
        }
    }