设计模式学习笔记(三)之 单例模式

今天是上班的第一天,由于没给安排活,也不知道干啥,于是随便翻翻csdn看到一篇关于单例模式的讲解,觉得写的很不错,讲的简单易懂,为了加深记忆(打发时间)写下这篇博客。
原博客地址:单例模式

1、什么是单例模式

从书上讲的来说,单例模式,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式,单例模式是指在内存中只会创建且仅创建一次对象的设计模式。简单来说就是整个程序有且仅有一个实例。

2、单例模式的两种类型

创建的单例模式有两种类型:

  • 懒汉式:需要时再去创建对象
  • 饿汉式:不管是否需要,在类加载的时候就创建单例对象

2.1 懒汉式

懒汉式在创建对象前需要进行判断对象是否实例化,如果已有实例化对象直接返回,没有则new一个后返回。

代码如下:

public static Singleton{
    private static Singleton singleton;
    
    private Singleton() {}
    
    public static Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

2.2 饿汉式

饿汉式在类加载时就创建好实例化的对象,待程序需要时直接返回该对象。

代码如下:

public static Singleton{
    private static final Singleton singleton = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance(){
        return singleton;
    }
}

到此两种类型的单例模式都已讲解完成,但是仍然存在某些问题,试想想,在多线程并发的情况下饿汉式单例模式还能正常使用,而懒汉式单例模式真的只有一个实例对象嘛?

3、懒汉式单例模式的优化改进

在并发的情况下,会存在还没有实例对象而多个线程进入到判断实例对象是否为null这一步,由于还没有创建实例对象,这些线程都会去创建实例,这样就出现了问题,所以我们需要对该步进行加锁!

改进后代码如下:

public static Singleton{
    private static Singleton singleton;
    
    private Singleton() {}
    
    public static Singleton getInstance(){
        if (singleton == null) {
            synchronized(Singleton.class) { // 某个线程获得该锁进行初始化
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        
        return singleton;
    }
}

因为需要两次判空,且对类对象加锁,该懒汉式写法也被称为:Double Check(双重校验) + Lock(加锁)

上面这段代码已经近似完美了,但是还存在最后一个问题:指令重排(这块是看完这篇博客后才知道的)

4. 使用volatile防止指令重排

创建一个对象,在JVM中会经过三步:

(1)为singleton分配内存空间

(2)初始化singleton对象

(3)将singleton指向分配好的内存空间

指令重排序是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能

在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报NPE异常。

使用volatile关键字优化后的代码:

public static Singleton{
    private static volatile Singleton singleton;
    
    private Singleton() {}
    
    public static Singleton getInstance(){
        if (singleton == null) {
            synchronized(Singleton.class) { // 某个线程获得该锁进行初始化
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        
        return singleton;
    }
}

5、总结

单例模式分为懒汉式和饿汉式两种写法,之前课堂上讲的由于没怎么听过对这些都不太熟悉,写完这篇博客后算是基本了解单例模式了,以后在实际代码中用到时也不会显得迷茫,要去考虑多线程的这种情况,因为实际项目和理想上的情况总是不一样的,需要考虑全面!

注意事项:

(1)在开发中如果对内存要求比较高,那么使用懒汉式写法,可以在特定时候才创建该对象;

(2)不必考虑内存情况则推荐使用饿汉式写法,因为简单不易出错!且没有任何并发安全和性能问题

(3)为了防止多线程环境下,因为指令重排序导致变量报NPE,需要在单例对象上添加volatile关键字防止指令重排序

最后修改:2020 年 07 月 17 日 10 : 10 AM
如果觉得我的文章对你有用,请随意赞赏