Java 单例和同步

Posted

技术标签:

【中文标题】Java 单例和同步【英文标题】:Java Singleton and Synchronization 【发布时间】:2012-06-25 07:44:13 【问题描述】:

请澄清我对单例和多线程的疑问:

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

【问题讨论】:

【参考方案1】:

是的,这是必要的。您可以使用几种方法通过延迟初始化实现线程安全:

严酷的同步:

private static YourObject instance;

public static synchronized YourObject getInstance() 
    if (instance == null) 
        instance = new YourObject();
    
    return instance;

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

Double check synchronization:

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;

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

Initialization on Demand:

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


public static YourObject getInstance() 
    return InstanceHolder.instance;

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

【讨论】:

警告 - 仔细检查同步。由于内存模型的“问题”,它不能在 Java 5 之前的 JVM 上正常工作。 -1 Draconian synchronizationDouble check synchronization getInstance()-方法必须是静态的! @PeterRader 他们并不需要成为static,但如果他们是的话可能更有意义。已按要求修改。 您实施的双重检查锁定不能保证有效。它实际上在您引用的双重检查锁定的文章中进行了解释。 :) 那里有一个使用 volatile 的示例,该示例适用于 1.5 及更高版本(双重检查锁定在 1.5 以下完全被破坏)。文章中还引用的按需初始化持有人可能是您的答案中更简单的解决方案。 @MediumOne AFAIK, r 不需要正确性。这只是避免访问 volatile 字段的优化,因为这比访问局部变量要昂贵得多。【参考方案2】:

此模式对实例进行线程安全的延迟初始化显式同步!

public class MySingleton 

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

     private MySingleton () 

     public static MySingleton getInstance() 
         return Loader.INSTANCE;
     

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

这就像魔术。

它实际上与 Jhurtado 的枚举模式非常相似,但我发现枚举模式是对枚举概念的滥用(尽管它确实有效)

【讨论】:

同步仍然存在,只是由 JVM 而非程序员强制执行。 我知道它对 JVM 没有任何影响,我只是说就自我记录的代码而言它对我有影响。我以前从未见过没有“final”关键字(或枚举)的Java中的所有大写字母,有点认知失调。对于全职编程 Java 的人来说,这可能不会有什么不同,但如果你来回跳语言,它会有助于明确。新手也一样。虽然,我相信人们可以很快适应这种风格。全部大写可能就足够了。不想挑剔,我喜欢你的帖子。 优秀的答案,虽然我没有得到它的一部分。您能否详细说明“此外,类加载器保证在您访问该类之前完成所有静态初始化 - 这就是为您提供线程安全的原因。” ,这对线程安全有何帮助,我对此有点困惑。 @wz366 实际上,虽然没有必要,但出于风格原因我同意(因为它实际上是最终的,因为没有其他代码可以访问它)应该添加final。完成。 如果getInstance 方法需要参数,我如何将参数传递给内部类中的MySingleton 构造函数?【参考方案3】:

如果您在 Java 中的多线程环境中工作并且需要保证所有这些线程都在访问一个类的单个实例,您可以使用 Enum。这将具有帮助您处理序列化的额外优势。

public enum Singleton 
    SINGLE;
    public void myMethod()  
    

然后让你的线程像这样使用你的实例:

Singleton.SINGLE.myMethod();

【讨论】:

【参考方案4】:

是的,您需要使getInstance() 同步。如果不是这样,可能会出现可以创建类的多个实例的情况。

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

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

【讨论】:

将对象创建代码放在同步块中会有所帮助,而不是使整个方法同步吗? @RaoG 否。您希望在同步块中同时检查 创建。您需要将这两个操作一起发生而不会中断,否则我上面描述的情况可能会发生。【参考方案5】:

枚举单例

实现线程安全的单例的最简单方法是使用枚举

public enum SingletonEnum 
  INSTANCE;
  public void doSomething()
    System.out.println("This is a singleton");
  

此代码在 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 ;
  

这在 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");
  


【讨论】:

【参考方案6】:

您还可以使用静态代码块在类加载时实例化实例并防止线程同步问题。

public class MySingleton 

  private static final MySingleton instance;

  static 
     instance = new MySingleton();
  

  private MySingleton() 
  

  public static MySingleton getInstance() 
    return instance;
  


【讨论】:

@Vimsha 其他几件事。 1. 你应该将instance 设为final 2. 你应该将getInstance() 设为静态。 如果你想在单例中创建一个线程,你会怎么做。 @arun-george 使用线程池,如果需要,使用单个线程池,如果你想确保你的线程永远不会死掉,则用 while(true)-try-catch-throwable 包围,无论如何什么错误?【参考方案7】:

在多线程环境中,在 Java 中实现 Singleton 的最佳方法是什么?

请参阅这篇文章了解实现单例的最佳方法。

What is an efficient way to implement a singleton pattern in Java?

当多个线程尝试同时访问 getInstance() 方法时会发生什么?

这取决于您实现该方法的方式。如果您使用没有 volatile 变量的双重锁定,您可能会得到部分构造的 Singleton 对象。

更多详情请参考这个问题:

Why is volatile used in this example of double checked locking

我们可以让单例的 getInstance() 同步吗?

在使用单例类时真的需要同步吗?

如果您通过以下方式实现 Singleton,则不需要

    静态初始化 枚举 LazyInitalaization with Initialization-on-demand_holder_idiom

更多详情请参考这个问题

Java Singleton Design Pattern : Questions

【讨论】:

【参考方案8】:
public class Elvis  
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () ...
 

来源:Effective Java -> Item 2

如果您确定该类将始终保持单例,则建议使用它。

【讨论】:

以上是关于Java 单例和同步的主要内容,如果未能解决你的问题,请参考以下文章

synchronized使用的正确姿势

synchronized使用的正确姿势

spring使用单例 线程怎么解决并发

Java单例---反射攻击单例和解决方法

java单例和多例

同步方法或同步块:在java中编写单例类时哪一个更好? [重复]