单例模式定义:
确定某个类只有一个实例,而且自行实例化并向系统提供这个实例。
使用场景:
创建对象需要消耗很多资源(如Android当中各种系统提供的Service:InflatorService, AMS, WMS...),或者全局状态管理同步类等。
单例模式写法:
- 饿汉模式
public class Singleton {
private static Singleton sInstance = new Singleton();
//私有化构造方法
private Singleton(){}
public static Singleton getInstance() {
return sInstance;
}
}
- 懒汉模式
public class Singleton {
private static SingleTon sInstance;
//私有化构造方法
private Singleton(){}
//保证多线程安全 同时每次进来都要获取锁, 浪费锁资源
public synchronized static Singleton getInstance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}
需要注意的是这里有一个误区:很多人认为饿汉模式的sInstance在声明的时候就new出来了,所以是在声明的时候就初始化了。饿汉模式中的sIntance到底是何时初始化的?从类的加载机制可以知道 是我们在调用getInstance 引用到sIntance的时候初始化的。当然如果饿汉模式SingleTon暴露出来了其他静态公共属性或方法,那么sIntance也会伴随这些公共属性或方法的访问而被初始化。所以我们说懒汉模式延迟初始化,实际上懒汉模式也是在调用getInstance()方法的时候进行初始化的,并没有达到懒加载的目的。
- 双重校验锁(Double Check Lock)
public class Singleton {
//添加volatile关键字 防止指令重排序
private volatile static Singleton sInstance;
//私有化构造方法
private Singleton(){}
//此处不加锁 保证性能
public static Singleton getInstance() {
if (sInstance == null) {
//加锁保证多线程安全
synchronized(Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();//创建对象
}
}
}
return sInstance;
}
}
为什么使用volatile关键字?
这段代码 sInstance = new Singleton(); 实际上在Java JVM中是下面的伪代码:
memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
sInstance=memory; //3:设置singleton指向刚排序的内存空间
当线程A执行到以上伪代码的时候,第2行和第3行代码可能发生重排序,因为重排序并不会影响运行结果,而且还可以提升性能,所以JVM中允许重排序。设想一个情景:如果2和3发生了重排序,也就是1->3->2,执行到3还未执行2的时候,有个线程B调用了getInstance方法,发现sInstance不为null,直接返回了sIntance,但此时sIntance并没有初始化完成,线程B就会访问到一个未初始化完成的对象。volatile关键字就是用来解决这个问题的,volatile 关键字修饰的变量不允许指令重排序
- 静态内部类模式(多线程安全)
public class Singleton {
//私有化构造方法
private Singleton(){}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
静态内部类模式是通过初始化锁来保证多线程安全的
总结
通过以上几种创建单例模式的理解,我们推荐使用的是Double Check Mode 和静态内部类模式。
以上内容均是我个人对单例模式的一些理解,如有错误,请留言告诉我哈。么么么哒。