设计模式之单例模式

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

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的控制器也是单例的

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

  • 单例对象占用资源少,不需要延迟加载 此时枚举好用于饿汉式

  • 单例对象占用资源多,需要延时加载 静态内部类好用于懒汉式

posted @ 2020-07-03 17:54  xuan_study  阅读(194)  评论(0)    收藏  举报