一直知道单例模式有4种,但是都没有写过。在这复习之际把这些东西写一边巩固下基础。
1.饿汉式
public class Singleton1 {
private Singleton1() {};//私有的无参构造器
private static Singleton1 instance = new Singleton1();
private static Singleton1 getInstance(){
return instance;
}
}
使用起来简单方便,但是在单例较多的情况下内存占用会比较高。
2.懒汉式
public class Singleton2 {
private Singleton2(){};
private volatile static Singleton2 instance;//加上volite防止指令重排
private static Singleton2 getInstance(){
if (instance == null) {
synchronized(Singleton2.class){//加锁防止多线程生成多个实例
if (instance == null) {
instance = new Singleton2();//指令重排序,先完成赋值,但构造函数还没执行完
}
}
}
return instance;
}
}
采用了双重检查,线程安全
用上面的例子,简单复习下volitile的用法 之后有时间可能会写个详细的
instance = new Singleton2()可以分解为三部分
1 memory=allocate();// 分配内存
2 ctorInstanc(memory) //初始化对象
3 instance=memory //设置instance指向刚分配的地址
JVM出于优化需要可能进行指令重排就会出现1->3->2的情况,多线程的情况下instance还没有初始化之前其他线程就会在外部检查到instance不为null,而返回还没有初始化的instance,就会有问题
JVM不会将volatile变量上的操作与其他内存操作一起重新排序,保证检测instance的状态时都是最新的
3.静态内部类
public class Singleton3 {
private Singleton3(){};
private static Singleton3 getInstance(){
return Holder.instance;
}
private static class Holder{
private static Singleton3 instance = new Singleton3();
}
}
JVM保证在类加载的过程中static代码块在多线程或者单线程下都正确执行,且仅执行一次。内部类不会在类的外部被使用,只有在调用getInstance()方法时才会被加载,解决了延迟加载以及线程安全的问题。
4.使用枚举
public enum Singleton4 {
INSTANCE;
private Singleton4(){};
}
应该是最简单的方法吧,枚举构造函数为私有,不能再创建枚举对象,枚举对象的声明和初始化都是在static块中,由JVM的ClassLoader机制保证了线程的安全性。
Singleton4.INSTANCE就是类Singleton4的唯一实例。