Bootstrap

Java单例模式

什么是单例模式

  • 单例模式是一种常用的软件设计模式,其定义是单例模式的类只能允许一个实例存在
  • 也就是说,只允许内存创建一次对象的设计模式.并提供一个获取方法以供使用.

什么情况下需要使用单例模式

  • 需要频繁的实例化和销毁的对象;
  • 有状态的工具类对象
  • 频繁访问数据库或文件对象;
  • 确保某个类只有一个对象的场景,比如一个对象需要消耗的资源过多,访问io、数据库,需要提供全局配置的场景

单例模式的代码实现

饿汉式

public class Singleton {//饿汉式
    private Singleton(){}//私有构造方法
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
}
  • 首先是私有化构造方法,杜绝了外部会创建对象的可能
  • 其次是方法为static.可以直接调用Singleton.getInstance(); 来获取实例.
  • 最后是饿汉式的特点,就是会使实例在类加载阶段被创建出来.从而后期需要的时候直接获取即可.

懒汉式

public class Singleton {//懒汉式
    private Singleton(){}//私有构造方法
    private static Singleton instance = null;
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
  • 首先懒汉式和饿汉式类似.私有化了构造方法
  • 其次懒汉式和饿汉式一样也是static修饰获取方法.可以直接调用Singleton.getInstance(); 方法来获取实例.
  • 最后一点也是懒汉式和饿汉式有明显区别的一点,那就是懒汉式的实例并不是随着类加载被创建的.而是在首次调用获取方法(Singleton.getInstance();)时才被创建出来的(懒加载/延时加载).

如果你看到这里要问我那个方法更好一些的话,那我只能说各有千秋.

  • 饿汉式可以提前将实例创建好,一直保持饥饿状态,这就使得你只要给它食物(调用获取方法),它就马上狼吞虎咽(直接就返回创建好的实例).不会再因为创建实例而浪费时间.
  • 而懒汉式虽然不能做到及时回应,但是它懒呀.它甚至懒得在你首次调用获取方法之前去占用内存.是不是特别懒呀.

单例模式的安全情况

看完了单例模式的Java代码,那你觉得单例模式的这两种方式在多线程下安全吗?

那我们就在单核和时间片轮转的情况下来观察观察是否存在不安全问题,以及如果存在不安全问题该怎么解决.

  • 首先我们来看看饿汉式

可以看出即使在多线程的极端情况下,饿汉式也不会有不安全问题.(饿汉式在多线程下安全)

  • 那懒汉式也是如此吗
    在这里插入图片描述

可以明显的看出懒汉式存在不安全问题,那该怎么解决这个不安全问题呢

  • 因为是多线程安全问题.那我们首先想到的肯定是synchronized关键字,那该怎么使用呢
    • 因为不安全问题在于instance = new Singleton(); 这句代码.那我们可以做的就是使用synchronized代码块将这段代码包起来,并且由于该方法为static方法,代码块的()中就只能填入Singleton.class来保证多个线程加的锁为同一个对象.如下:
synchronized (Singleton.class) {
    if(instance == null){
        instance = new Singleton();
    }
}
  • 那这就结束了吗,仔细想想,代码块确实帮助我们解决了不安全问题,并且我们还发现当创建实例以后,就和饿汉类似了,但是此时多个线程调用获取方法时,都需要阻塞等待其他线程释放锁资源.这样就大大增加了运行时间,故我们需要在外层再加一层if判断来防止已经创建实例后,下次再调用获取方法而因为等待锁耗费的时间.如下:
if (instance == null) {
    synchronized (Singleton.class) {
        if(instance == null){
            instance = new Singleton();
        }
    }
}
  • 然而到这里还没有结束,我们还需要使用volatile关键字来保证内存可见性.这里是为了防止Java虚拟机的一些底层优化.(Java虚拟机的一些优化使得代码可能不更新instance,从而使得后续获取操作可能继续进入不安全状态).到此懒汉式才进入了多线程下的安全状态.完整代码如下:
public class Singleton {//懒汉式
    private Singleton(){}//私有构造方法
    private static volatile Singleton instance = null;//volatile使用关键字防止因为内存优化而出现instance不及时更新的问题
    public static Singleton getInstance(){
        if (instance == null) {//故用多重判断来解决下面的问题,后续进入方法的线程就不会进入synchronized块,而提升效率
            synchronized (Singleton.class) {//使用这种方式来规避线程安全问题,但却会严重影响效率.因为每次执行都会进入synchronized块判断
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

到此线程安全的单例模式也就介绍完了.

;