单例与同步

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

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

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

Consider the case where you have two threads that call getInstance() at the same time. Now imagine T1 executes just past the instance == null check, and then T2 runs. At this point in time the instance is not created or set, so T2 will pass the check and create the instance. Now imagine that execution switches back to T1. Now the singleton is created, but T1 has already done the check! It will proceed to make the object again! Making getInstance() synchronized prevents this problem.

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

是的,这是必要的。有几种方法可以用来实现线程安全的惰性初始模式:

严酷的同步:

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,由于这是由 ClassLoader控制的,因此不需要额外的同步。

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

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

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

Singleton.SINGLE.myMethod();

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

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()方法中被访问,所以当第一次调用 getInstance()时,Loader类装入。此外,类装入器保证在访问类之前完成所有静态初始化——这就是为什么线程安全的原因。

就像魔法一样。

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

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

public class MySingleton {


private static final MySingleton instance;


static {
instance = new MySingleton();
}


private MySingleton() {
}


public static MySingleton getInstance() {
return instance;
}


}

Enum singleton

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

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


}

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

参考这篇文章获得实现 Singleton 的最佳方法。

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

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

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

更多细节请参考以下问题:

为什么在这个双重检查锁定的示例中使用易失性

我们能使 singleton 的 getInstance ()同步吗?

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

如果以下面的方式实现 Singleton,则不需要

  1. 静态初始化静态初始化
  2. 枚举
  3. 使用按需初始化 _ holder _ idiom 的 LazyInitialization

更多细节请参考这个问题

Java 单例设计模式: 问题

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

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

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