15
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 的特殊使用。

4

9 回答 9

19

你是对的,多个线程可以尝试创建一个 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;
      }
 }
于 2013-02-25T22:42:58.663 回答
16

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

于 2013-05-30T06:50:24.833 回答
7

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

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

class MyClass {
    public static Resource getInstance() {
        return ResourceLoader.RESOURCE;
    }

    private static final class ResourceLoader {
        private static final Resource RESOURCE = new Resource();
    }
}
于 2013-02-25T22:59:01.357 回答
1

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

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

于 2013-02-25T22:44:53.643 回答
0

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

根据教程

Reads and writes are atomic for all variables declared volatile

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

于 2013-02-25T22:44:37.440 回答
0

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

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

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

更多信息

于 2013-02-25T22:45:08.213 回答
0

你是对的,在这种情况下,由于你描述的种族,资源可能会被构造两次。如果您想在 Java 5+ 中实现单例(没有显式锁定),请使用枚举单例,如在 Java 中实现单例模式的有效方法是什么?.

于 2013-02-25T22:53:25.723 回答
0

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

于 2013-02-25T23:43:00.333 回答
0

这是我将 volatile 和同步添加在一起的建议。

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

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;
    }
}
于 2016-12-20T18:23:36.187 回答