在java中使用volatile的单例

时间:2013-02-25 22:40:42

标签: java concurrency

class MyClass
{
      private static volatile Resource resource;

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

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

8 个答案:

答案 0 :(得分:17)

你是对的,多个线程可以尝试创建一个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;
      }
 }

答案 1 :(得分:14)

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

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

根据tutorial

Reads and writes are atomic for all variables declared volatile
  

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

答案 4 :(得分:0)

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

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

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

  4.   

更多info

答案 5 :(得分:0)

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

答案 6 :(得分:0)

首先,以这种方式使用Singleton,你实际上是在创建一个全局对象,这是一种不好的做法。我估计你会使用Enums。

答案 7 :(得分:0)

我建议添加volatile& amp;同步在一起。

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

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;
    }
}