饿汉式1:通过静态变量来实例化单例
根据类加载技术,静态代码只执行一次,所以保证了实例是单例的。
根据类加载技术,由于实例化对象是在静态代码中进行的,所以可以提保证在调用方法前实例已经被创建了。
饿汉式会造成内存浪费
饿汉式2:通过静态代码块来实例化单例
饿汉式2原理同饿汉式1,区别是通过静态代码块来实例化单例
饿汉式3:通过枚举来实例化单例
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。
枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
如果不考虑浪费空间,首选枚举方式。
枚举类在定义的时候就给出了本类的所有的实例,并且在类加载过程中就完成了实例化。
所有的枚举类都继承自Enum类。
懒汉式1:通过一次对象是否为空的判断来决定是否需要创建实例
虽然懒汉式1不是线程安全的,但在单线程下可用,所以也应该记住。
懒汉式2:直接给getInstance方法加锁来保证线程安全
虽然懒汉式2是可用的,但由于绝大多数getInstance方法都是读操作,每次调用都加锁性能就不是最高的
懒汉式3:是对读操作的优化,避免了每次调用都加锁
双重检查
第一层判断在同步代码快外,判断是否直接返回对象
第二层判断在同步代码块内,判断是否需要写对象
使用volatile保证正确性,因为volatile关键字可以保证可见性和有序性
在多线程情况下可能会出现空指针问题,原因是JVM在实例化对象时会进行优化和指令重排序操作。
使用volatile关键字解决空指针异常问题,因为volatile关键字可以保证可见性和有序性。
懒汉式4:利用静态内部类创建单例
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
静态内部类在被访问时才被加载,保证了懒汉性。
静态属性保证了单例。
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
破坏单例模式
序列化破坏单例模式
将单例对象序列化存储到文件中后,通过反序列化就可以创建出不同的单例类对象。
反射破坏单例模式
通过反射调用单例类的私有构造方法可以创建出不同的单例类对象。
解决序列化破坏单例模式:
在Singleton类中添加readResolve()
方法,这个方法就会在反序列化时被反射调用。如果类中定义了该方法,直接将方法返回值作为反序列化的结果,如果没有定义,则返回新new出来的对象。
这套机制源于ObjectInputStream类的定义。
解决反射破坏单例模式:
在Singleton类的构造方法中增加是否是第一次调用构造方法的判断,如果不是第一次调用就直接抛出异常不创建对象,另外需要增加锁来保证线程安全。
枚举类无法被破坏破坏单例模式
Enum类的clone方法被定义为了final,不允许被克隆。
Enum类在反序列化时最终拿到的还是单例对象。
反射在创建对象时会判断是否为枚举类,如果是枚举类则会抛出异常反射失败。