单例模式

全局唯一

场景


  • 全局唯一的共享资源

静态版本


实现

Sample
1
2
3
4
5
6
7
8
public class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}

public static Singleton getInstance(){
return INSTANCE;
}
}

使用

Sample
1
Singleton ins = Singleton.getInstance();

特征


  • private构造,禁止外部创建
  • private static final实例,类加载初始化
  • public static获取实例方法

优缺点


  • 利用static特性实现单例,类加载初始化,不能延迟加载
  • 如果支持序列化接口,还要额外考虑readResolve()以及transient

如果是支持序列化,还要阻止反序列化可能产生新实例

Sample
1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton implements Serializable{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}

public static Singleton getInstance(){
return INSTANCE;
}

private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}

更严格的话,反射也可以破坏单例,需要阻止构造函数被调用

Sample
1
2
3
4
5
6
7
private Singleton(){
synchronized(Singleton.class) {
if(INSTANCE != null){
throw new RuntimeException();
}
}
}

枚举版本


实现

Sample
1
2
3
public enum Singleton{
INSTANCE;
}

使用

Sample
1
Singleton ins = Singleton.INSTANCE;

优缺点


  • 利用枚举特性实现单例,极简

延迟加载版本


多线程共享的资源想被用到时再初始化,同时要避免资源被不同线程反复初始化

整体同步

整个方法同步,同步机制保证了排他性和可见性
但是保护范围过大,每次调用都进行同步,即使是完成初始化后,性能差

Sample
1
2
3
4
5
6
public synchronized static Resource getResource(){
if (resource == null){
resource = new Resource();
}
return resource;
}

Double-Check

内部加锁,双重检查
第一层检查能够使得初始化完成后,无需再保护
第二层检查在锁内,避免再次初始化

Sample
1
2
3
4
5
6
7
8
9
10
public static Resource getResource() {
if (resource == null) {
synchronized(Singleton.class){
if (resource == null) {
resource = new Resource();
}
}
}
return resource;
}

注意仅仅是上述方法并不能保证正确
resource是引用类型,由于java内存模型允许无序写入,可能先分配空间,引用指向空间,再进行构造
因此变量变为非null时,构造函数可能还没执行,此时共享变量指向一个尚未完成的引用, 其他线程使用可能会调用到尚未完成构造的实例进而出现问题
Java5后可以搭配volatile保证完整

1
private volatile static Resource resource;

这里没有final, 因为不是在构造函数里赋值

静态内类

再包一层,既利用了静态特性,又不至于一开始就加载
静态内类定义时不会被加载,只有使用内部的静态变量时才会被加载,这个加载过程JVM会保证唯一性,无需保护,另外final保证了变量被安全发布

Sample
1
2
3
4
5
6
7
8
9
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

应用


java.lang.Runtime