什么是单例模式?
说白了就是单实例(一个对象)保证一个类中只能有一个对象,在内存中只能开辟一块内存空间,就叫做单例模式(个人理解),也是最简单的设计模式之一。各大框架底层都运用了单例设计模式(spring 的 bean,servlet (违单例,构造器没有私有化!!!))。
单例模式分类:
饿汉式、懒汉式、DCL懒汉式、静态内部类方式、枚举类方式:
饿汉式: 非常饿,上来就将对象创建出来,不会延迟加载
//创建饿汉式单例
public static class Hungry {
//单例模式核心思想,构造方法私有化
private Hungry() {
}
//饿汉式,非常的饿,所以上来就将该单实例创建出来
//单实例定义为static静态的在类加载时创建只会加载一次该实例
//设置为final的,因为该单实例是多线程共享的,并且在多线程并发下
//可能对该单实例有修改行为,则可能会有线程安全问题,加上final关键字
//解决线程安全问题
private static final Hungry HUNGRY = new Hungry();
//创建获得单实例的方法
public static Hungry getHungry() {
//将该单实例返回
return HUNGRY;
}
/*
以上代码存在的问题:
上来就讲该单实例创建了,如果该类中有一些资源较大的对象,可能会造成内存空间的浪费
*/
private byte[] bytes1 = new byte[1024*1024];
private byte[] bytes2 = new byte[1024*1024];
private byte[] bytes3 = new byte[1024*1024];
private byte[] bytes4 = new byte[1024*1024];
//以上代码在饿汉式单例模式下会造成内存空间的浪费
}
懒汉式:非常的懒,只有在需要用到该单实例的时候才会将单实例创建
//创建懒汉式单例
public class Lazy {
//单例核心思想:构造器私有化
private Lazy(){
}
//该单例不用被final修饰,因为被final修饰之后必须赋初始值,类加载的时候就创建了又变成饿汉式了
// 哈哈哈
private static Lazy lazy = null;
//设置获取单实例的方法
public static Lazy getLazy() {
//首先判断该单实例是否为null,如果为null则说明是第一次调用,直接创建对象返回即可
if (lazy == null) {
//由于该单实例没有被final关键字修饰,是多线程共享的存在线程安全问题,所有需要使用
//同步代码块进行同步线程,静态变量使用类锁同步
synchronized(Lazy.class) {
lazy = new Lazy();
}
}
return lazy;
}
}
DCL懒汉式:使用了双检测锁机制,使用了指令重排的方式来提高程序的执行效率
//创建DCL懒汉式单实例
public class DCLLazy {
//构造器私有化
private DCLLazy() {
}
//创建单实例使用volatile修饰
private static volatile DCLLazy dclLazy = null;
//创建返回方法
public static DCLLazy getDCLLazy() {
//使用双重检测锁机制
if (dclLazy == null) {
//第一次调用
synchronized(DCLLazy.class) {
if (dclLazy == null) {
dclLazy = new DCLLazy();
}
}
}
return dclLazy ;
}
}
静态内部类方式: 在静态内部类中创建单实例,调用时才会被执行,既延迟加载,又保证了数据安全性
//创建静态内部类 单例
public class OuterClass {
//构造器私有
private OuterClass() {
}
//创建静态内部类,用来给单实例对象赋值
public class InnerClass {
private static final OuterClass = new OuterClass() ;
}
//创建获取方法
public OuterClass getOuterClass() {
//返回该静态内部类中的静态单例对象
return InnerClass.OuterClass ;
}
//静态内部类其实就是饿汉式的改良版,也是一上来就创建单例对象,不过是在内部类中的创建
//需要用的时候,再从静态内部类中获取该单例对象,(静态类类加载)
}
枚举实现单例模式:解决了反射机制破坏单例模式的问题
//创建枚举类
public enum Enum {
//创建该单实例
ENUM;
//构造器私有化
private Enum() {
}
//创建返回方法
public static Enum getEnum() {
return Enum.ENUM;
}
}
为什么会有枚举方式来实现单例?
饿汉式、懒汉式、DCL懒汉式、静态内部类的方式都可以使用反射机制打破构造器的私有化,从而打破单例规则,而枚举的构造器则不能被打破封装!!!
//测试反射机制打破单例模式
public class BreakSingleton {
//获取该单例类的字节码文件
Class singleClass = Class.forName("single.DCLLazy");
//获取该类的无参构造器
Constructor declaredConstructor = singleClass .getDeclaredConstructor(null);
//打破无参构造器的私有化
declaredConstructor.setAccessible(true);
//打封装之后 创建该单实例对象
DCLLazy dCLLazy1 = (DCLLazy)declaredConstructor.newInstance();
DCLLazy dCLLazy2 = (DCLLazy)declaredConstructor.newInstance();
System.out.println(DCLLazy1 == DCLLazy1 );
//结果为true,可得单例模式被打破了
}
而如果采用的是枚举类的话在使用反射机制打破封装的时候则会报“java.lang.NoSuchMethodException”找不到无参构造器异常
@Test
public void test(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//测试单例模式,饿汉式、懒汉式、DCL懒汉式、静态内部类方式、枚举方式
Class enumClass = Class.forName("testSingle.Enum");
Constructor declaredConstructor = enumClass.getDeclaredConstructor(null);
Enum enum1 = (Enum)declaredConstructor.newInstance();
Enum enum2 = (Enum)declaredConstructor.newInstance();
System.out.println(enum1 == enum2);
}
为什么在枚举中定义了无参构造器,但是在打破无参构造器的封装的时候汇报找不到无参构造的异常?
枚举:JDK1.5推出的,其实枚举底层就是一个class 不过是被包装了,反编译枚举生成的.class字节码文件,可得枚举类就是一个继承了Enum类的实体类,枚举的无参构造器最终在编译阶段也会被转为有参构造器这就是为什么,当你打破枚举的无参构造器的时候会报异常。
使用反编译工具查看字节码得:
//枚举反编译后的文件
package testSingle;
public final class Enum extends Enum
{
public static final Enum ENUM;
private static final Enum $VALUES[];
public static Enum[] values()
{
return (Enum[])$VALUES.clone();
}
public static Enum valueOf(String name)
{
return (Enum)Enum.valueOf(testSingle/Enum, name);
}
//有参构造器转为了无参构造器!!!!!!!!!!!!
private Enum(String s, int i)
{
super(s, i);
}
public static Enum getInstance()
{
return ENUM;
}
static
{
ENUM = new Enum("ENUM", 0);
$VALUES = (new Enum[] {
ENUM
});
}
总结 单例模式
饿汉式:线程安全的,但是没有延迟加载,可能会造成内存空间的浪费
懒汉式:延迟加载了,不过由于加了类锁,执行效率较慢
DCL懒汉式:延迟加载了,使用指令排查法,和双重检测锁机制提高了线程的执行效率,不过由于JMV底层内部模型的问题,可能运行时会出现问题(不建议使用)
静态内部类:延迟加载了,又是线程安全的
枚举类:没有延时加载,效率高,线程安全
创建单例,优先使用枚举。一般的话不考虑反射的前提下建议使用静态内部类的方式创建!