设计模式:创建型->单例模式
创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
什么是单例模式
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
两种类型
- 懒汉式:在真正需要使用对象时才去创建该单例类对象
- 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
其实比较好理解的
饿汉式
饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可。我们目前可以简单认为在程序启动时,这个单例对象就已经创建好了。
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 与数据库的连接等。