关于多线程:Java Singleton和Synchronization

Java Singleton and Synchronization

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

  • 在多线程中,在Java中实现单例的最好方法是什么?环境?
  • 当多个线程试图访问getInstance()时会发生什么?同时的方法?
  • 我们能做单件的getInstance()synchronized吗?
  • 使用单例类时,是否确实需要同步?

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

严酷的同步:

1
2
3
4
5
6
7
8
private static YourObject instance;

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

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

双重检查同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

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

按需初始化:

1
2
3
4
5
6
7
private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
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的枚举模式非常相似,但我发现枚举模式滥用了枚举概念(尽管它确实有效)。


如果您正在爪哇中使用多线程环境,并且需要保证所有线程都访问类的单个实例,则可以使用枚举。这将有助于处理序列化的额外优势。

1
2
3
4
5
public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

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

1
Singleton.SINGLE.myMethod();


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

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

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


埃努姆辛格尔顿

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

1
2
3
4
5
6
public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

自从在Java 1.5中引入了EnUM,这个代码就起作用了。

双重检查锁定

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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之前)

这个实现在加载类时实例化单例,并提供线程安全性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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");
  }

}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}


1
2
3
4
public class Elvis {
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

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

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


What is the best way to implement Singleton in Java, in a multithreaded environment?

有关实现单例的最佳方法,请参阅本文。

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

What happens when multiple threads try to access getInstance() method at the same time?

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

有关详细信息,请参阅此问题:

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

Can we make singleton's getInstance() synchronized?

Is synchronization really needed, when using Singleton classes?

如果以以下方式实现单例,则不需要

  • 静态初始化
  • 枚举
  • lazyinitalaization with initialization-on-demand oulder_习语
  • 请参阅此问题以了解更多详细信息

    Java单体设计模式:问题