单例模式
什么是单例
何为单例模式,通俗的讲就是:让整个程序中,只有单一的一个实例。也就是说,我们在程序中不会创建多次对象实例,只会创建一次对象实例,且该单一的对象对整个程序共享。
所以,我们可以得出,单例模式的基本类图如下:
而在实际运用中,我们有 6 种单例模式的写法。下面我们具体来讲一下。
饿汉模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
饿汉模式在类加载时候就完成初始化了,所以后续需要访问时候,他都是已经存在的了,这避免了线程同步的问题,使得其是线程安全的。
懒汉模式
懒汉模式指的是懒加载,让单例需要被使用到的时候再去加载,避免不必要的浪费。
线程不安全
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
为什么这种是线程不安全的呢?因为可能存在两个线程同时判断该 instance 为空的情况,然后破坏了单例模式。
线程安全
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
此处的代码相对于上述不安全的代码,多了一个在 getInstance()
方法中加入同步锁的代码,这样子理论上就可以实现线程安全了。
双重检查模式
上述懒汉模式中,在 getInstance()
中加入同步锁,会使得每次使用都同步,但是实际上,同步的情况是较少的。这样子会造成不必要的同步开销。并且,其同步并不一定真的同步了。
在基于
偏序关系
的Happens-Before内存模型
中,指令重排技术大大提高了程序执行效率,但同时也引入了一些问题。
竞态条件
会导致instance
引用被多次赋值,使用户得到两个不同的单例。
下面我们使用双重检查模式来解决上述的问题
public class Singleton {
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance() {
if ( instance == null ) { //当instance不为null时,仍可能指向一个“被部分初始化的对象”
synchronized (Singleton.class) {
if ( instance == null ) {
instance = new Singleton();
}
}
}
return instance;
}
}
第一次检查是为了不必要的同步,第二次检查是线程同步后,Singleton 等于 null 时候才创建实例。
静态内部类的单例模式
public class Singleton {
private Singleton(){
}
public static synchronized Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder{
private static final Singleton sInstance = new Singleton();
}
}
这个单例模式,既可以实现懒加载,又可以实现线程安全。是类加载和懒加载的融合。
枚举单例
public enum Singleton {
INSTANCE;
public void doSomeThing(){
}
}
枚举的肯定是单例且线程安全的,但是一般我们都不会用到这种方式。
综上,我们学习了六种单例模式,其中只有一类基本的懒加载是线程不安全的,我们要避免使用这种懒加载。
参考
volatile关键字的作用、原理 - 猴子007 - 博客园 (cnblogs.com)
《Android进阶之光》