Java单例和同步

Ric*_*vis 109 java singleton multithreading synchronization

请澄清我对Singleton和Multithreading的疑问:

  • 在多线程环境中,在Java中实现Singleton的最佳方法是什么?
  • 当多个线程同时尝试访问getInstance() 方法时会发生什么?
  • 我们可以制作单身人士getInstance() synchronized吗?
  • 使用Singleton类时是否真的需要同步?

Jef*_*rey 201

是的,这是必要的.使用延迟初始化可以使用多种方法来实现线程安全:

严厉同步:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}
Run Code Online (Sandbox Code Playgroud)

该解决方案要求每个线程同步,而实际上只需要前几个.

双重检查同步:

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}
Run Code Online (Sandbox Code Playgroud)

此解决方案确保只有尝试获取单例的前几个线程必须经历获取锁定的过程.

按需初始化:

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}
Run Code Online (Sandbox Code Playgroud)

此解决方案利用Java内存模型保证类初始化以确保线程安全.每个类只能加载一次,只有在需要时才会加载.这意味着第一次getInstance被调用,InstanceHolder将被加载instance并将被创建,并且由于这是由ClassLoaders 控制的,因此不需要额外的同步.

  • 警告 - 请仔细检查双重同步.由于内存模型的"问题",它无法与Java 5之前的JVM一起正常工作. (22认同)
  • -1`Draconian synchronization`和`Double check synchronization` getInstance() - 方法必须是静态的! (3认同)
  • 你实现双重检查锁定不能保证工作.它实际上在您引用的用于双重检查锁定的文章中进行了解释.:)有一个例子,使用volatile可以正常工作1.5和更高(双重检查锁定只是在1.5以下的普通破坏).文章中引用的按需初始化持有者可能是您答案中更简单的解决方案. (3认同)
  • @PeterRader他们不需要*为'静态`,但如果它们可能更有意义.按要求修改. (2认同)
  • @AlexLehmann这是你想要使用的任意对象. (2认同)
  • @MediumOne AFAIK,正确性不需要`r`.这只是一个避免访问volatile字段的优化,因为这比访问局部变量要昂贵得多. (2认同)

Boh*_*ian 65

此模式在没有显式同步的情况对实例执行线程安全的延迟初始化!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}
Run Code Online (Sandbox Code Playgroud)

它的工作原理是因为它使用类加载器为您免费执行所有同步:MySingleton.Loader首先在getInstance()方法内部访问Loader该类,因此该类getInstance()在第一次调用时加载.此外,类加载器保证在您访问类之前完成所有静态初始化 - 这就是为您提供线程安全性的方法.

这就像魔术一样.

它实际上非常类似于Jhurtado的枚举模式,但我发现enum模式滥用了enum概念(尽管它确实有效)

  • 同步仍然存在,它只是由JVM而不是程序员强制执行. (10认同)
  • 我理解它对JVM没有任何影响,我只是说它对我来说就自我记录的代码而言有所不同.我之前从未见过没有"最终"关键字的Java中的所有大写字母(或枚举),得到了一些认知不和谐.对于那些全职编程Java的人来说,它可能没有什么区别,但如果你来回跳转语言,那么有助于明确.新手同样如此.虽然,我相信人们可以很快适应这种风格; 所有上限都可能足够了.不要吝啬,我喜欢你的帖子. (2认同)
  • @ wz366实际上,虽然没有必要,但我同意样式的原因(因为它实际上是最终的,因为没有其他代码可以访问它).应该添加`final`.完成. (2认同)

jhu*_*ado 21

如果您正在使用Java中的多线程环境并且需要保证所有这些线程都在访问类的单个实例,则可以使用Enum.这将有助于您处理序列化的附加优势.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}
Run Code Online (Sandbox Code Playgroud)

然后让你的线程使用你的实例,如:

Singleton.SINGLE.myMethod();
Run Code Online (Sandbox Code Playgroud)

  • 枚举方法仅适用于JDK1.5或更高版本。 (2认同)

Ole*_*ksi 8

是的,你需要进行getInstance()同步.如果不是,可能会出现可以制作类的多个实例的情况.

考虑你有两个同时调用的线程的情况getInstance().现在假设T1刚刚执行instance == null检查,然后T2运行.此时,实例未创建或设置,因此T2将通过检查并创建实例.现在假设执行切换回T1.现在创建了单例,但是T1已经完成了检查!它将继续制作该对象!制作getInstance()同步防止这个问题.

有几种方法可以使单例线程安全,但是getInstance()同步可能是最简单的.


Dan*_*van 6

Enum singleton

实现线程安全的Singleton的最简单方法是使用Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}
Run Code Online (Sandbox Code Playgroud)

自从Java 1.5中引入Enum以来,此代码就可以运行了

双重检查锁定

如果你想编写一个在多线程环境中工作的"经典"单例(从Java 1.5开始),你应该使用这个.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class){
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance ;
  }
}
Run Code Online (Sandbox Code Playgroud)

这在1.5之前不是线程安全的,因为volatile关键字的实现是不同的.

早期加载Singleton(甚至在Java 1.5之前工作)

此实现在加载类时实例化单例并提供线程安全性.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}
Run Code Online (Sandbox Code Playgroud)