单身人士没有挥发性成员

时间:2015-05-09 20:28:14

标签: java multithreading

假设我有下一堂课:

public class Singleton{

  private static Singleton _instance; 

  public static Singleton getInstance(){

   if(_instance == null){
         synchronized(Singleton.class){
           if(_instance == null)
           _instance = new Singleton();
         }
   }
   return _instance;
 }

例如,我们有两个线程 A B ,它们同时尝试执行 getInstance()方法。我是否正确理解了这个过程:

  1. 线程 A 进入 getInstance()方法并获取锁定;
  2. 线程 B 也会进入 getInstance()方法并被阻止;
  3. 线程 A 创建新的Singleton()对象并释放锁,现在 _instance 变量的最后值对于线程乙?或者线程 B 仍然可以拥有 _instance 变量的副本,该副本与主存储器不同步( _instance = null)?

3 个答案:

答案 0 :(得分:4)

线程B在同步时被阻止,当它继续时,它将看到更改的字段_instance != null,因此不构造一个但返回现有的。

稍后出现的所有其他线程都会看到实例被设置,甚至不会锁定。

问题:你的代码不完整,你需要使用volatile来确保不通过synchronized的线程(大多数,希望)仍然只能看到一个完全发布的单例对象。

Java内存模型仅保证最终字段被初始化。对于所有其他人,您需要safe publish,这可以通过以下方式实现:

  • 通过正确锁定的字段(JLS 17.4.5)
  • 交换引用
  • 使用静态初始化程序执行初始化存储(JLS 12.4)
  • 通过易失性字段(JLS 17.4.5)交换引用,或者作为此规则的结果,通过AtomicX类交换引用
  • 将值初始化为最终字段(JLS 17.5)。

避免volatile的最简单方法(或者将对象发布到其他线程也是安全的原子引用)是使用普通的Object初始化,这是JVM提供的有效且健壮的单例(但不是懒惰):

class Singleton
{
    private static final Singleton HIGHLANDER = new Singleton();
    private Singleton() { } // not accessible
    public static getSingleton() { return HIGHLANDER; }
}

JDK在内部使用这个类似的构造与"Holder" objects来实现相同的简单和健壮的模式,但是以一种懒惰的方式:

class Singleton
{
    private Singleton() { } // not accessible
    private static Class LazyHolder {
        private static final Singleton LAZY_HIGHLANDER = new Singleton();
    }
    public static Singleton getInstance() {
        return LazyHolder.LAZY_HIGHLANDER;
    }
}

两种方法都不需要volatile变量访问(在DCL情况下需要)或同步(它由JVM隐式完成,它通过类锁进行初始化)。

答案 1 :(得分:3)

您在此处显示的内容称为double-checked locking

静态变量属于,而不属于线程。两个线程都将看到正确的值,但编译器可能会优化读取,以便两次都不检查静态变量。因此,you should declare the variable with the volatile keyword

请注意,在版本5之前的Java版本中,即使使用volatile变量,这也可能无法正常工作。以前,赋值可以将部分构造的对象分配给变量。现在构造函数必须返回,然后才能继续进行赋值。这将在任何现代版本的Java中正常工作。

答案 2 :(得分:1)

据我所知,可能存在两个问题。 线程B可能会也可能不会看到最新值。 线程B可能会看到部分构造的对象,因为构造函数会执行很多操作,并且JVM决定更改代码的顺序。

使它变为volatile可以解决这两个问题,因为它会在关系之前强制执行,并阻止JVM重新排序代码,并更新其他线程中的值。