单例设计模式的实现
单例模式(Singleton Pattern)是JAVA中最简单的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式。
结构
- 单例类——只能创建一个实例的类
- 访问类——使用单例类
实现
- 饿汉式——类加载就会导致该单例对象被创建
- 懒汉式——类加载不会导致该单例对象被创建,而是首次使用该对象时才会被创建
饿汉式实现
方式一
为了保证全局中只有一个对象,那么我们就在该单例类中自己定义一个对外暴露的变量,来存储其的实现对象,别人要用?来我这里拿!
为什么要用static修饰呢?因为静态变量与静态方法是属于对象的,让你可以不用创建对象实例就可以直接通过类名调用!
//Singleton.class
//构造方法,改写成private目的是不让别人访问
private Singleton(){}
//创建本类对象
private static Singleton instance = new Singleton();
//为外界提供获取方法
public static Singleton getInstance() {
return instance;
}
这样我们就可以通过以下方式来直接获取此单例类:
Singleton instance = Singleton.getInstance();
方式二
我们可以通过枚举的方式来实现单例模式
public enum Singleton {
INSTANCE;
}
此方式简单粗暴,不存在线程安全问题,且只会被装载一次下,还是所有单例实现中唯一一种不会被破坏的单例实现模式,所以如果不考虑内存占用问题的话,此方式为首选!
懒汉式实现
饿汉式在类加载时就创建了单例类的实例,如果我们做了一些操作使该类被加载(例如调用其子类),但我们并没有使用这个对象,那么就会造成内存的占用。
所以我们要实现懒汉式的加载,在类加载时不创建实例,而是在类被调用时进行创建。
方式一
思路:
将new实例的过程放在getter方法中,如果没有调用getter方法,类就不会被加载。
//Singleton.class
//构造方法,改写成private目的是不让别人访问
private Singleton() {}
//声明Singleton变量
private static Singleton instance;
//getter
public static Singleton getInstance() {
//判断Instance是否为null,如果为null,则创建其对象
if(instance == null) {
instance = new Singleton();
}
return instance;
}
但这样也存在问题:
如果我们有两个线程,它们同时执行该方法来获取Singleton实例,线程一执行到if语句块内时间到了,转为线程二执行,那么线程二也会进入到if判断中,这样就会执行两次instance = new Singleton();方法,导致该实例不唯一。
那么我们如何解决呢?
很简单,只要让该方法同一时间只能有一个进程执行即可:
//加一个同步锁synchronized
public static synchronized Singleton getInstance() {
//判断Instance是否为null,如果为null,则创建其对象
if(instance == null) {
instance = new Singleton();
}
return instance;
}
但这样解决也显得有些多余,因为线程同步问题只有在第一次调用时才存在,只要第一次调用时安全产生了一个实例,以后再调用就无需再进行调用了
所以我们这里提出双重检查锁模式,通俗来说就是将同步锁放到if判断中
public static synchronized Singleton getInstance() {
//判断Instance是否为null,如果为null,则创建其对象,如果不为空,也不需要抢占锁,可以直接返回对象
if(instance == null) {
//加一个同步锁synchronized
synchronized (Singleton.class) {
//再判断一次
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
双重检查锁模式虽然看起来完美无缺,但其实仍存在问题:在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
想解决上述问题,我们需要使用volatile关键字,以此来保证可见性和有序性。
private static volatile Singleton instance;
至此为止,我们通过双重检查锁模式案例就完成啦!
方式二
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。
静态属性由于被static修饰,保证只被初始化一次,并且严格保证实例化顺序。
//Singleton.class
//私有构造方法
private Singleton() {}
//定义一个静态内部类
private static class SingletonHolder {
//在内部类中声明并初始化外部类对象
private static final Singleton INSTANCE = new Singleton();
}
//提供getter
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
静态内部类单例模式是一种优秀的单例模式,在开源项目中比较常用。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。