首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >单例无易失性成员

单例无易失性成员
EN

Stack Overflow用户
提问于 2015-05-09 20:28:13
回答 3查看 564关注 0票数 4

假设我下节课:

代码语言:javascript
复制
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可见吗?或者线程B仍然可以有自己的_instance变量的副本,这与主存(_instance=null)不同步吗?
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-05-09 20:55:23

线程B在同步过程中被阻塞,当它继续执行时,它将看到已更改的字段_instance != null,因此它不会构造字段,而是返回现有的字段。

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

问题:您的代码是不完整的,您需要使用易失性来确保没有经过同步(希望是其中大多数)的线程仍然只看到一个完全发布的单例对象。

Java内存模型只保证初始化最后的字段。对于所有其他人,您需要一个安全发布,这可以通过以下方法实现:

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

避免易失性(或原子引用也可以安全地将对象发布到其他线程)的最简单方法是使用正常的对象初始化,这是JVM提供的有效和健壮的单例(但不是惰性):

代码语言:javascript
复制
class Singleton
{
    private static final Singleton HIGHLANDER = new Singleton();
    private Singleton() { } // not accessible
    public static getSingleton() { return HIGHLANDER; }
}

JDK在内部与“持物”物品一起使用类似的构造来实现相同的简单和健壮的模式,但以一种惰性的方式实现:

代码语言:javascript
复制
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隐式完成的,它通过类锁保护初始化)。

票数 5
EN

Stack Overflow用户

发布于 2015-05-09 20:38:47

你在这里展示的东西叫做二次检查锁定

静态变量属于类,而不是线程。两个线程都会看到正确的值,但是编译器可能会优化读取,这样静态变量就不会同时被选中。出于这个原因,关键字

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

票数 3
EN

Stack Overflow用户

发布于 2015-05-09 20:34:10

据我所知可能存在两个问题。线程B可能看到也可能看不到最新的值。线程B可能会看到一个部分构造的对象,如果构造函数做了很多事情,JVM决定改变代码的顺序。

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30144683

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档