单例模式几种写法
所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例存在。类似Spring中的IOC 配置,bean实例化默认都是单例模式的。
单例的四种写法:
1.饿汉式单例
//饿汉式
public class Singleton{
//1.私有的唯一的静态实例变量,在类加载的时候就创建好单例对象
private static final Singletion instance= new Singleton();
//2.私有的构造函数,确保不能在类的外部访问该类的构造函数
private Singleton{}
//3.通过静态工厂方法返回类的唯一实例
private static Singletion getInstance(){
return instance;
}
}
优点:此写法是线程安全的
缺点:会增大初始化类和创建对象的开销
2.懒汉式
public class Singleton {
//1.一个私有的指向自己的静态变量
private static Singleton instance = null;
// 2.私有的构造方法,保证不能从外部创建对象
private Singleton{}
//3.真正获取实例的时候才进行初始化,如果多线程访问 synchronized会阻塞其他线程
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton(); // lazy load
return instance;
}
}
优点: 延时加载,只有在使用的时候进行初始化
问题:同步锁粒度在多线程进行获取实例的时候会出现阻塞,加大了因锁竞争的性能开销
3.double-check双重检查锁定
public class Singleton {
//1.volatile保证可见性
private static volatile Singleton instance = null;
// 2.私有的构造方法,保证不能从外部创建对象
private Singleton{}
public static Singleton getInstance() {
//3.instance使用volatile标识可以保证所有线程访问同一共享变量的可见性问题
if (instance == null) {// double check (jdk1.5+)
//4.synchronized虽然保证了原子性,但不能保证 同步方法里面的顺序性
//5. instance = new Singletion(),其实由三步组成
// 1).分配对象内存空间 2).初始化对象 3).设置instance指向刚分配的内存地址
//6.由于不同cpu的内核,JMM重排序粒度不一样,instance在第2,3步见可能会被重排序
// 执行过程中instance不为null,但获取对象 并不是真正赋值对象(旧值或空值)
synchronized (Singleton.class)
if (instance == null){
instance = new Singleton();
}
}
return instance;
}
}
优点: 用双重验证的方式,降低因多线程因为同步锁而造成的开销,同步锁保证模块执行的原子性,volatile保证实例可见性
缺点:同步锁模块内部方法体instance = new Singleton();并不是原子性操作(像count++也不是原子性操作) 还是可能造成JIT编译器对代码进行重排序 导致对象的值不正确
4.静态内部类写法
public class Singleton {
//1.在第一次被引用时被加载
private static class Holder { // lazy class
static final Singleton instance = new Singleton();
}
//2.保证不能进行构造
private Singleton(){ }
//3.通过静态方法获取
public static Singleton getInstance() {
return Holder.instance;
}
}
优点:线程安全的,且在使用时才会被进行初始化操作,优于上面三种方式
5.单例 枚举写法
public enum SingletonEnum {
instance;
/*
* do something
*/
public void doSomething(){
System.out.println("do something");
}
}
总之,懒汉,恶汉,双重校验锁,静态内部类,枚举。
恶汉:因为加载类的时候就创建实例,所以线程安全(多个ClassLoader存在时例外)。缺点是不能延时加载。
懒汉:需要加锁才能实现多线程同步,但是效率会降低。优点是延时加载。
双重校验锁:麻烦,在当前Java内存模型中不一定都管用,某些平台和编译器甚至是错误的,因为instance = new Singletion()这种代码在不同编译器上的行为和实现方式不可预知。
静态内部类:延迟加载,减少内存开销。因为用到的时候才加载,避免了静态field在单例类加载时即进入到堆内存的permanent代而永远得不到回收的缺点(大多数垃圾回收算法是这样)。
枚举:很好,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。但是失去了类的一些特性,没有延迟加载,用的人也太少了~~
可参考: