1.饿汉式
public class SingleTon{
private static SingleTon INSTANCE = new SingleTon();
private SingleTon(){}
public static SingleTon getInstance(){
return INSTANCE;
}}
2.懒汉式
public class SingleTon{
private static SingleTon INSTANCE = null;
private SingleTon(){}
public static SingleTon getInstance() {
if(INSTANCE == null){
INSTANCE = new SingleTon();
}
return INSTANCE;
}
}
3.双重校验锁懒汉模式
public class SingleTon{
private static SingleTon INSTANCE = null;
private SingleTon(){}
public static SingleTon getInstance(){
if(INSTANCE == null){
synchronized(SingleTon.class){
if(INSTANCE == null){
INSTANCE = new SingleTon();
}
}
return INSTANCE;
}
}
}
双重检查锁定失败的问题归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
singleton = new Singleton();
该语句非原子操作,实际是三个步骤。
-
给 Singleton 分配内存;
-
调用 Singleton 的构造函数来初始化成员变量;
-
将给 singleton 对象指向分配的内存空间(此时 singleton 才不为 null );
虚拟机的指令重排序
执行命令时虚拟机可能会对以上3个步骤交换位置 最后可能是132这种 分配内存并修改指针后未初始化 多线程获取时可能会出现问题。
当线程A进入同步方法执行singleton = new Singleton();代码时,恰好这三个步骤重排序后为1 3 2,
那么步骤3执行后 singleton 已经不为 null ,但是未执行步骤2,singleton对象初始化不完全,此时线程B执行 getInstance() 方法,第一步判断时 singleton 不为null,则直接将未完全初始化的singleton对象返回了。
解决
如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的,同时还会禁止指令重排序。
所以使用volatile关键字会禁止指令重排序,可以避免这种问题。使用volatile关键字后使得 singleton = new Singleton();语句一定会按照上面拆分的步骤123来执行。
private volatile static SingleTon INSTANCE = null;
另一个问题
单例模式并不是绝对安全的,可以通过反射来破坏,只有枚举安全类是安全的。
4.静态内部类单例模式
饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存,懒汉式单例类线程安全控制烦琐,而且性能受影响
public class Singleton {
private static class HolderClass{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return HolderClass.instance;
}
}