java面试经常问的一个问题就是单例怎么实现,这个问题主要考察的就是延迟加载和java的内存机制,按照通常的标准答案是这样的:
class Singleton {
private static volatile Singleton value;
private Singleton() {}
public static Singleton get() {
if (value == null) {
synchronized(Singleton.class) {
if (value == null) {
value = new Singleton();
}
return value;
}
}
}
}
这个方法理论上是没问题的,但是实际使用中,是有问题的。
首先,一个singleton就要写一个类,对于大一点的项目,有可能有多个使用单例的场景,岂不是每个都要写一个Singleton类,这就麻烦了。
其次,可以看到这个单例的构造函数是private,但是平时写的类,几乎不可能只有一个private的构造函数。
总之,这种写法不太实用,主要还是在面试时考察面试者对Java的基础是否了解。
实际的单例是怎么样的,可以参考spring的源码
public class SingletonSupplier<T> implements Supplier<T> {
@Nullable
private final Supplier<? extends T> instanceSupplier;
@Nullable
private final Supplier<? extends T> defaultSupplier;
@Nullable
private volatile T singletonInstance;
@Override
@Nullable
public T get() {
T instance = this.singletonInstance;
if (instance == null) {
synchronized (this) {
instance = this.singletonInstance;
if (instance == null) {
if (this.instanceSupplier != null) {
instance = this.instanceSupplier.get();
}
if (instance == null && this.defaultSupplier != null) {
instance = this.defaultSupplier.get();
}
this.singletonInstance = instance;
}
}
}
return instance;
}
}
可以看到,实际情况下,Spring并没有每个需要单例的地方都写一个单例,而是提供一个supplier来创建单例。
这里还有一个小技巧,因为this.singletonInstance已经被volatile修饰,读this.singletonInstance是从主内存读取,这里赋值给instance,只用读一次,速度更快。
当然,如果要确保严格的单例,那么还要确保SingletonSupplier的对象也是单例的,但是从Spring代码看,并没有这么严格,只要求每个对象的singletonSupplier.get()返回的是单例。