请澄清我对单例和多线程的疑问:
- 在多线程环境中,在 Java 中实现 Singleton 的最佳方法是什么?
- 当多个线程同时尝试访问
getInstance()
方法时会发生什么? - 我们可以做单例
getInstance()
synchronized
吗? - 使用单例类时真的需要同步吗?
请澄清我对单例和多线程的疑问:
getInstance()
方法时会发生什么?getInstance()
synchronized
吗?是的,这是必要的。您可以使用几种方法通过延迟初始化来实现线程安全:
严苛的同步:
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
s 控制的,因此不需要额外的同步。
这种模式在没有显式同步的情况下对实例进行线程安全的延迟初始化!
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 的枚举模式非常相似,但我发现枚举模式滥用了枚举概念(尽管它确实有效)
如果您在 Java 中的多线程环境中工作并且需要保证所有这些线程都在访问一个类的单个实例,那么您可以使用 Enum。这将具有帮助您处理序列化的额外优势。
public enum Singleton {
SINGLE;
public void myMethod(){
}
}
然后让您的线程使用您的实例,例如:
Singleton.SINGLE.myMethod();
是的,您需要进行getInstance()
同步。如果不是这样,可能会出现可以创建类的多个实例的情况。
考虑有两个线程同时调用getInstance()
的情况。现在想象一下 T1 在instance == null
检查之后执行,然后 T2 运行。此时实例尚未创建或设置,因此 T2 将通过检查并创建实例。现在想象执行切换回 T1。现在单例已创建,但 T1 已完成检查!它将继续制作对象!使getInstance()
同步可以防止这个问题。
有几种方法可以使单例线程安全,但getInstance()
同步可能是最简单的。
枚举单例
实现线程安全的单例的最简单方法是使用枚举
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");
}
}
您还可以使用静态代码块在类加载时实例化实例并防止线程同步问题。
public class MySingleton {
private static final MySingleton instance;
static {
instance = new MySingleton();
}
private MySingleton() {
}
public static MySingleton getInstance() {
return instance;
}
}
在多线程环境中,在 Java 中实现 Singleton 的最佳方法是什么?
请参阅这篇文章以了解实现 Singleton 的最佳方法。
当多个线程同时尝试访问 getInstance() 方法时会发生什么?
这取决于您实现该方法的方式。如果您使用没有 volatile 变量的双重锁定,您可能会得到部分构造的 Singleton 对象。
有关更多详细信息,请参阅此问题:
我们可以使单例的 getInstance() 同步吗?
使用单例类时真的需要同步吗?
如果您通过以下方式实现 Singleton,则不需要
有关更多详细信息,请参阅此问题
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis () {...}
}
来源:有效的 Java -> 第 2 项
它建议使用它,如果您确定该类将始终保持单例。