定义
单例模式确保了某个类中只存在唯一的实例,并且自行实例化向整个系统提供这个实例。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。常用于计算机系统中线程池、缓存、日志对象、对话框、打印机、显卡驱动程序对象的设计。
特点
单例类只有一个实例单例模式必须自己创建自己的唯一对象,必须构造器私有防止外部创建对象!单例模式必须提供这一实例给所有其他对象
单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。
二、存在的问题多线程模式下使用单例模式需要格外的小心!
如果唯一实例未创建,有两个线程同时调用创建方法,那么他们同时会监测出没有唯一的实例而创建实例,这样会导致两线程创造多个单例实例!违反了单例模式中实例唯一的原则!
解决方法:为知识类是否已经实例化的变量提供一个互斥锁。
三、单例模式八种写法 1、饿汉式(静态常量)【可以使用】class Hungry{ // 唯一实例 private static final Hungry HUNGRY = new Hungry(); // 不允许外围创建,构造器私有 private Hungry(){} public static Hungry getInstance(){ return HUNGRY; }}
优点:写法较简单,在类装载时完成了加载,不会出现刚刚提到的线程问题。
缺点:在类装载时就完成了加载,可能调用该类的其他方法或静态属性就会完成加载,没有达到Lazy Loading的效果,如果从始至终都没有使用该实例,就会出现内存资源的浪费
2、饿汉式(静态代码块)【可以使用】
class Hungry { private static final Hungry INSTANCE; static { INSTANCE = new Hungry(); } private Hungry() {} public static Hungry getInstance() { return INSTANCE; }}
实现方法与第一种方法相同,因此优缺点也与方法一一致。
3、懒汉式(线程不安全)【不可用】
public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; }}
优点:起到了Lazy Loading的效果,能够做到懒加载节约内存资源。
缺点:很明显在多线程环境下两个线程同时进入if(singleton == null)语句就会创建多个实例。
4、懒汉式(线程安全,同步方法)【不推荐】
public class Singleton { private static Singleton singleton; private Singleton() {} public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; }}
优点:解决了第三种实现中的线程不安全问题,同时也能够解决Lazy Loading问题。
缺点:效率太低了,每一次调用getInstace方法都需要同步。本来在第一次调用后直接return就可以,但还是要进行同步特别影响效率。
5、懒汉式(线程不安全,同步代码块)【不可用】
public class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance{ if(singleton == null){ synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; }}
这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
6、双重检查(DCL懒汉式)【推荐】
public class 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; }}
Double-Check概念通过两次if(singleton == null)检查,八正了线程安全,同时实例化代码只会执行一次,后面再次访问,if判断后会直接返回单例对象。
优点:线程安全;lazy loading;效率很高;
单例类实例为什么需要添加volatile?
JUC中学过指令重排,CPU可能会将原本顺序执行的指令执行顺序打乱,但打乱后指令执行结果不会受到影响,就是指令重排。对于new Singleton()这个操作本身就不是一个原子性操作,可以分为以下三步
分配内存空间,此时该空间内容为空执行构造方法,初始化对象修改内存空间内容将这个对象指向这个空间,此时sinleton指针被修改为非空地址 正常情况下按照1 -> 2 -> 3的顺序执行与按照1 -> 3 -> 2顺序执行结果不会被影响,因此CPU可能会将指令重排使得执行次序为1 -> 3 -> 2。
假设有进程A、B,A进程按照1 -> 3 -> 2 顺序执行到 3 时,此时singleton指向了一个未添加内容的内存地址,即:singleton非空,但是singleton指向地址存储值为空。B进程进入函数后进行if(singleton==null)判断,判断为false直接返回singleton,此时返回的引用指向的地址因为进程A还没有执行到 2 操作所以没有值,可能会引发错误。
因此为了防止指令重排带来的一系列问题,该方法创建单例类实例需要添加volatile防止指令重排。
7、静态内部类【推荐】public class Singleton { private Singleton() {} private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; }}
对于SingletonInstace只有在调用getInstace()时才会被加载完成实例化,相较于饿汉式这样的加载方式能够节省内存空间,确保了lazyLoading。
同时静态属性只有第一次类加载时才会初始化,所以JVM也能保证线程的安全性。
优点:线程安全;lazyLoading;效率高
8、枚举【特别推荐】
为什么是枚举?
前7种方法实现单例模式中,都忽略了Java中一个重要创建对象的方法,反射!
通过反射能够随意修改单例类构造器的访问权限并创建对象,几乎没有方法能够防止反射创建单例模式的实例对象。因此前面7中方法或多或少都会有弊端。
但是反射无法破坏枚举类,在newInstance方法中有这么一个判断,使得使用枚举类实现的单例模式不会受到反射的影响!
枚举类的所有成员均只有唯一实例,与静态类、静态成员变量相同,都是使用JVM特性实现单例模式。那么现在尝试实现单例模式:
public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); }}public static void main(String []args){ Singleton.INSTANCE.doSomthing()}
如果仍然想通过反射破坏枚举类,通过javap -p .class文件反编译 .class 文件可以得知(可能会得到无参构造器,但是使用更高级的反编译器jad.exe反编译会得到正确构造器),枚举类中拥有一个双参构造器
使用反射尝试强行创建实例破坏单例
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Constructor
得到结果:抛出异常
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objectsat java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:492)at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)at Main.main(Main.java:15)
可以看到使用Enum实现的单例模式能够防治反射恶意破坏!