创建型模式之单例模式

2019-12-02  本文已影响0人  雪飘千里

创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程,即创建对象的同时隐藏创建逻辑。

1、使用场景

什么是单例模式呢,单例模式(Singleton)又叫单态模式,它出现目的是为了保证一个类在系统中只有一个实例,并提供一个访问它的全局访问点。从这点可以看出,单例模式的出现是为了可以保证系统中一个类只有一个实例而且该实例又易于外界访问,从而方便对实例个数的控制并节约系统资源而出现的解决方案。

使用单例模式当然是有原因,有好处的了。在下面几个场景中适合使用单例模式:

1、有频繁实例化然后销毁的情况,也就是频繁的 new 对象,可以考虑单例模式;
2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象;
3、频繁访问 IO 资源的对象,例如数据库连接池或访问本地文件;
4、有状态的工具类对象。

2、volatile双重检查锁机制

public class SingleInstance {
    private static volatile SingleInstance singleInstance;
    private Singleton(){};
    public static SingleInstance getSingleInstance(){ //1
        //非空则跳过,因为只有首次初始化才有安全问题,保证了初始化之后,线程不会阻塞,提高了性能
        if (singleInstance == null) { //2. 以一次检查
            synchronized(SingleInstance.class){ //3 加锁
                //voliatile能保证可见性,但不能保证原子性,加锁保证线程并发情况下,也只有一个实例
                if (singleInstance == null) { //4 第二次检查
                    singleInstance = new SingleInstance();//5 创建实例 
                }
            }
        }
        return singleInstance;
    }
}

注:原子性,简单的说,就是多线程下,简单的赋值和读取操作,具体请参考Java内存模型

优缺点:

Java指令执行的过程:

volatile修饰词作用:

上述的singleInstance类变量假如没有用volatile关键字修饰的,则会导致这样一个问题:
多线程情况下,在B线程执行第5步时,A线程执行到第4步,由于重排序的原因,B线程还没有完成instance引用的对象的初始化,但是A线程已经读取到singleInstance不为null,这时候就会导致空指针异常。

第5步的代码创建了一个对象,这一行代码可以分解成3个操作:
memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory;  // 3:设置instance指向刚分配的内存地址

根源在于代码中的2和3之间,可能会被重排序。例如:

memory = allocate();  // 1:分配对象的内存空间
instance = memory;  // 3:设置instance指向刚分配的内存地址
// 注意,此时对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象
这在单线程环境下是没有问题的,但在多线程环境下会出现问题,像上面的,B线程会看到一个还没有被初始化的对象。

2和3的重排序不影响线程A的最终结果,但会导致线程B在第二步判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象。

注:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

面试中有时会让现场手写一个简单的单例模式,如果能写出volatile方式,并且能把volatile机制讲清楚,会加分不少。

3、恶汉式

class SingleInstance2 {
    private static final SingleInstance3 singleInstance3 = new SingleInstance3();
    public static SingleInstance3 getSingleInstance3() {
        return singleInstance3;
    }
}

优缺点:

4、加锁懒汉式

class SingleInstance3 {
    private static SingleInstance2 singleInstance2;
    public static synchronized SingleInstance2 getSingleInstance2(){
        if (singleInstance2 == null) {
            singleInstance2 = new SingleInstance2();
        }
        return singleInstance2;
    }
}

优缺点:

上一篇下一篇

猜你喜欢

热点阅读