设计模式之单例模式

单例模式的优点之为什么要使用单例模式

1.由于单例模式只生成一个实例,减小系统性能开销,当一个对象的产生需要多个资源时(读取配置文件、产生其他依赖)那么我们可以在应用启动时直接创建该对象实例,然后永久驻留内存。

2.单例模式可以在系统设置全局访问点,优化共享资源访问。例如可以设计一个单例类,负责所有数据表的映射处理。

五种单例模式的实现方式

饿汉式单例模式(线程安全,调用效率高,不能延时加载)
package singal;
/**
  饿汉式单例  一上来就把对象加载了 可能会存在浪费内存的问题
 **/
public class Hungry {

    private Byte aByte1[]=new Byte[1024];
    private Byte aByte2[]=new Byte[1024];
    private Byte aByte3[]=new Byte[1024];
    private Byte aByte4[]=new Byte[1024];

    //构造函数私有化
    private Hungry(){ }

    private final static Hungry HUNGRY =new Hungry(); //类初始化时立即加载对象!

    //方法没有同步所以调用效率高
    public static Hungry getInstance(){
        return HUNGRY;
    }
}
懒汉式单例模式(调用效率不高、可以延时加载)
package singal;

//懒汉式

public class LazySingal {

    private LazySingal(){

    }
    private static  LazySingal LAZY_SINGAL;  延迟加载,也称为懒加载 真正用的时候我们才加载

    public static synchronized LazySingal getInstance(){  //如果不加同步的话会存在创建对象不唯一的情况
            if (LAZY_SINGAL==null){
            LAZY_SINGAL=new LazySingal();
        }
        return LAZY_SINGAL;
    }
}
DCL懒汉式单例模式(volatile)(懒汉式以及双重检测锁式)
package singal; 

import javax.sound.midi.Soundbank;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
 懒汉式单例
 **/
public class LazyMan {

    private static Boolean XUAN =false;   //自定义布尔常量防止反射创建新对象

    //构造函数私有
    private LazyMan(){

        synchronized (LazyMan.class){
            if (XUAN==false){
                XUAN=true;
            }else throw new RuntimeException("不要试图通过反射来破坏单例!");
        }
    }

    private volatile static LazyMan lazyMan;  //延迟加载,也称为懒加载 真正用的时候我们才加载

    //双重检测的懒汉式单例 DCL懒汉式
    public static LazyMan getInstance(){
        if (lazyMan==null){
           synchronized (LazyMan.class){
               if (lazyMan==null){
                   lazyMan= new LazyMan();
                   /**
                    * 为什么要加volatile?
                    * 因为在创建对象的时候
                    * 1.分配内存空间
                    * 2.执行构造方法初始化对象
                    * 3.把这个对象指向内存空间
                    * 原本顺序为123 但是在内存中是完全有可能发生指令排序出现问题的现象的比如132 这不算错误但是对于线程来说就					 * 是会出现问题
                    * 如果A线程执行132 B线程进来时原本还没完成构造的A内存空间被误判为非空
                    * 于是B线程直接走return lazyMan 但是这空间却是虚无的
                    */
               }
           }
        }

        return lazyMan;
    }
    
    //测试多线程并发
    /*public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }*/

    //通过反射来破坏单例
    public static void main(String[] args) throws Exception {

        //拿到XUAN字段
        Field xuan = LazyMan.class.getDeclaredField("XUAN");
        //将private失效
        xuan.setAccessible(true);

        //懒汉式实例对象
        //LazyMan instance = LazyMan.getInstance();
        //获取空构造
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        //使private失效
        declaredConstructor.setAccessible(true);
        //构造实例对象
        LazyMan newInstance = declaredConstructor.newInstance();
		//set false就是为了能够再创建对象
        xuan.set(newInstance,false);
        LazyMan newInstance1 = declaredConstructor.newInstance();
        //对象比较
        System.out.println(newInstance1);
        System.out.println(newInstance);
        System.out.println(newInstance1==newInstance);
    }
}
singal.LazyMan@4e50df2e        <<-----------上面的输出结果 明显是单例被反射破坏了 结果产生了两个对象
singal.LazyMan@1d81eb93
false
静态内部类(线程安全、调用效率高、可以延时加载)
package singal;

// 静态内部类
public class Holder {

    //构造函数私有化
    private Holder(){

    }

    public static Holder getInstance(){   //只有你去调用内部类的时候才会加载初始化对象
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER =new Holder();
    }
}

以上都是可以通过反射直接破坏单例模式的

枚举(反射和反序列化不能破坏的单例)(调用效率高,不能延时加载)
//由JVM从根本上提供保障
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    Class<?> caller = override ? null : Reflection.getCallerClass();
    return newInstanceWithCaller(initargs, !override, caller);
}

/* package-private */
T newInstanceWithCaller(Object[] args, boolean checkAccess, Class<?> caller)
    throws InstantiationException, IllegalAccessException,
           InvocationTargetException
{
    if (checkAccess)
        checkAccess(caller, clazz, clazz, modifiers);

    if ((clazz.getModifiers() & Modifier.ENUM) != 0) //用枚举创建对象
        throw new IllegalArgumentException("Cannot reflectively create enum objects");//用反射破坏枚举单例会报错

    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(args);
    return inst;
}
package singal;

import java.lang.reflect.Constructor;

/**
 * 什么是枚举?一个被命名整数常数集合
 **/
public enum  EnumSingle { //这是一个枚举类

    INSTANCE;  //单例枚举对象  

    public  EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle single1 = EnumSingle.INSTANCE;
        //EnumSingle single2 = EnumSingle.INSTANCE;

        //我们尝试反射创建一下对象   发现是不行的
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);//String.class,int.class是反编译出来构造参数
        declaredConstructor.setAccessible(true);
        EnumSingle single2 = declaredConstructor.newInstance();
        System.out.println(single1);
        System.out.println(single2);
    }
}
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:493)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)
	at singal.Test.main(EnumSingle.java:28)    <<<------  控制台输出结果

单例模式的应用场景

  • Windows的任务管理器就是典型的单例模式(不论你打开多少次任务管理器 只能打开一个)
  • 应用程序的日志应用,因为要实现实时的动态更新,一般只能有一个实例去操作。
  • 项目中读取配置文件的类,一般只有一个对象。(Properties加载DataSource)
  • 数据库连接池也是采用单例模式
  • spring的IOC容器中的每一个bean都是单例的这样能方便容器管理
  • SpringMVC的控制器也是单例的

如何选用合适的单例模式?

  • 单例对象占用资源少,不需要延迟加载 此时枚举好用于饿汉式
  • 单例对象占用资源多,需要延时加载 静态内部类好用于懒汉式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值