单例模式算是最简单最容易理解的设计模式,在项目中会经常使用。写法呢,很多种,最好的或者最适合的是哪一个?
设计一个单例要考虑哪些因素呢:
- 单例
- 延迟创建或加载
- 线程安全
写法1
public class Singleton {
private static Singleton ourInstance = new Singleton();
public static Singleton getInstance() {
return ourInstance;
}
private Singleton() {
}
}
这个被称作饿汉式,用空间换时间,类一加载就创建单例的实例,用的时候直接返回就行了。但是这样会过早的创建实例,从而降低内存的使用效率。
写法2
public class Singleton {
private static Singleton ourInstance ;
public static Singleton getInstance() {
if (ourInstance == null){
ourInstance = new Singleton();
}
return ourInstance;
}
private Singleton() {
}
}
这个是用时间换空间,叫懒汉式,实际用到的时候再去创建实例。 如果你的程序不牵扯到多线程,那么这个可以了。 但这个写法在多线程的情况下就有问题了,如果同时有两个线程运行到判断ourInstance是不是null的if语句,而且ourInstance的确没有创建时,那么两个线程就会创建一个实例,那么现在就不能保证单例模式的要求了。
写法3
加锁
public class Singleton {
private static Singleton ourInstance ;
public static Singleton getInstance() {
if (ourInstance == null){
synchronized (Singleton.class){
if (ourInstance == null) {
ourInstance = new Singleton();
}
}
}
return ourInstance;
}
private Singleton() {
}
}
在创建实例前加同步及判断,并且使用两个if判断来保证效率,保证了在多线程下也能够保证会有一个实例了。
写法4
写法3的代码感觉比较复杂
public class Singleton {
public static Singleton getInstance() {
return Nested.singleton;
}
private Singleton() {
}
static class Nested{
static Singleton singleton = new Singleton();
}
}
在内部定义了一个私有内部类,类型Nested
只有在Nested.singleton
中被用到。因此当我们第一次试图通过getInstance()
去取实例的时候,Nested才会加载并实例化一个Singleton实例。
写法5
使用原子操作的方式
public class Singleton {
private static AtomicReference<Singleton> instance = new AtomicReference<>(null);
public static Singleton getInstance() {
if (instance.get() == null){
instance.compareAndSet(null, new Singleton());
}
return instance.get();
}
private Singleton() {
}
}
其中 compareAndSet(V expect, V update)
:
如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
这种方式既能够保证延迟加载又能保证原子性及实例的唯一性,代码也相对比较简洁。