设计模式:创建型->单例模式

创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
 
以下参考这篇文章:https://zhuanlan.zhihu.com/p/160842212
 

什么是单例模式

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
 

两种类型

    • 懒汉式:在真正需要使用对象时才去创建该单例类对象
    • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用

其实比较好理解的

饿汉式

饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可。我们目前可以简单认为在程序启动时,这个单例对象就已经创建好了

public class Singleton {
    //饿汉式
    private static Singleton singleton=new Singleton();
    private Singleton() 
    { 
        
    }
    public Singleton getInstance()
    {
        return singleton;
    }

}

构造方法私有,这样就不能在外部new出对象,只能使用getInstance获取对象。

优缺点:

懒汉式

public class Singleton {
    //懒汉式
    private static Singleton singleton;
    private Singleton()
    {}
    public Singleton getInstance()
    {
        if(singleton==null)
        {
            singleton=new Singleton();
        }
        return singleton;
    }
}

最基础的写法,类加载的时候没有实例化,使用时才new

 

但这种写法会出现并发问题

如果两个线程同时判断singleton为空,那么它们都会去实例化一个Singleton对象,这就变成双例了。所以,我们要解决的是线程安全问题。

 加上同步方法或者同步代码块

public static synchronized Singleton getInstance() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}
// 或者
public static Singleton getInstance() {
    synchronized(Singleton.class) {   
        if (singleton == null) {
            singleton = new Singleton();
        }
    }
    return singleton;
}

但这两种方式都不好,我们要锁住的是 创建对象这一过程

获取对象这个过程没必要加锁

public static Singleton getInstance() {
    if (singleton == null) {  // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
        synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化
            if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

这种写法相对较好:

  • 之所以进入判断后还要再判断一次,是因为可能其他线程已经把它实例化了
  • 锁住的为什么是 Singleton.class 锁住一个类

 

指令重排问题

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

(1)为singleton分配内存空间

(2)初始化singleton对象

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

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

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

 

 

public class Singleton {
    
    private static volatile Singleton singleton;
    
    private Singleton(){}
    
    public static Singleton getInstance() {
        if (singleton == null) {  // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
            synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化
                if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
    
}

最终代码,把对象用volatile修饰

 

单例模式使用场景

创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

 

 

 

 

posted @ 2021-03-31 09:33  将来的事  阅读(50)  评论(0编辑  收藏  举报