Java Singleton和同步


117

请阐明有关单例和多线程的查询:

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

Answers:


211

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

Draconian同步:

private static YourObject instance;

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

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

仔细检查同步

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;
}

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

按需初始化

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

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

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


23
警告-请仔细检查同步。由于内存模型出现“问题”,它无法在Java 5之前的JVM上正常工作。
Stephen C

3
-1 Draconian synchronizationDouble check synchronizationgetInstance()方法必须是静态的!
Grim

2
@PeterRader他们不需要static,但如果这样,则可能更有意义。根据要求修改。
杰弗里

4
您执行的双重检查锁定无法保证正常工作。实际上,在您引用的双重检查锁定文章中对此进行了解释。:)那里有一个使用volatile的示例,该变量在1.5及更高版本上正常工作(双重检查的锁定在1.5以下才被完全破坏)。本文中还引用了对按需持有人的初始化,这可能是您回答中的一种更简单的解决方案。
stickj 2015年

2
r正确性不需要@MediumOne AFAIK 。这只是避免访问volatile字段的一种优化,因为这比访问局部变量要昂贵得多。
杰弗里

69

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

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的枚举模式非常相似,但是我发现该枚举模式滥用了枚举概念(尽管确实有效)


11
同步仍然存在,它只是由JVM而不是程序员强制执行。
Jeffrey 2012年

@Jeffrey你当然是对的-我在(搜索结果中输入了所有信息)
Bohemian

2
我了解它对JVM没什么影响,我只是说,就自我记录的代码而言,它对我没有影响。在(或枚举)之前,我从未见过Java中没有“ final”关键字的所有大写字母,引起了一些认知上的失调。对于全职使用Java进行编程的人来说,这可能不会有什么不同,但是如果您来回跳转语言,则有助于明确。同上新手。虽然,我确信可以很快适应这种风格。所有的上限可能就足够了。不要故意挑剔,我喜欢你的帖子。
Ruby 2014年

1
很好的答案,尽管我没有得到它的一部分。您能否详细说明一下:“进一步,类加载器保证在您访问该类之前所有静态初始化都已完成-这就是线程安全性的原因。” ,这对线程安全性有何帮助,对此我有些困惑。
gaurav jain 2014年

2
实际上,@ wz366虽然不是必需的,但出于样式原因我同意(因为它实际上是最终的,因为没有其他代码可以访问它)final。做完了
波西米亚风格

21

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

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

然后让您的线程像下面这样使用您的实例:

Singleton.SINGLE.myMethod();

8

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

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

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


将对象创建代码放在Synchronized块中,而不是使整个方法同步,是否有帮助?
RickDavis 2012年

@RaoG号。您需要在同步块中同时进行检查创建。您需要将这两个操作同时发生而不会中断,否则我可能会遇到上述情况。
Oleksi 2012年

7

枚举单例

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

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");
  }

}

2

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

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}

@Vimsha还有其他一些事情。1.你应该做instance最后2.你应该做getInstance()静态。
John Vint 2014年

如果要在单例中创建线程,该怎么办。
阿伦·乔治

@ arun-george使用一个线程池,如果需要的话使用一个线程池,如果想确保线程永不死,无论发生什么错误,都可以使用while(true)-try-catch-throwable包围。
tgkprog 2015年

0

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

有关实现Singleton的最佳方法,请参考这篇文章。

在Java中实现单例模式的有效方法是什么?

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

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

有关更多详细信息,请参考此问题:

为什么在双重检查锁定的示例中使用volatile

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

使用Singleton类时,真的需要同步吗?

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

  1. 静态初始化
  2. 枚举
  3. 带有初始化on-demand_holder_idiom的LazyInitalaization

请参阅此问题以获取更多详细信息

Java Singleton设计模式:问题


0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

资料来源:有效的Java->项目2

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.