Mybatis系列9:单例模式在Mybatis查找文件路径中的应用

1. 什么是单例模式

1.1 单例模式的概念

单例模式是一种比较常见的设计模式,在整个系统中,单例类只能有一个实例对象,且需要自行完成示例,并始终对外提供同一实例对象。
因为单例模式只允许创建一个单例类的实例对象,避免了频繁地创建对象,所以可以减少GC 的次数,也比较节省内存资源 ,加快对象访问速度。例如,数据库连接池、应用配置等一般都是单例的。在下面介绍的 VFS 中,也涉及单例模式的使用。单例模式有很多种写法,但是有些写法在特定的场景下,尤其是多线程条件下无法满足实现单一实例对象的要求,从而导致错误。
解决的问题:一个全局使用地方类频繁的创建和销毁。
如何解决:判断系统是有已经有这个单例,如果有则返回,没有则创建。
关键设计:构造函数私有,单例类自己创建唯一的实例,提供public 的访问方法。

单例模式的优点
只生成一个实例,减少的系统的开销,特别是当一个对象的产生需要比较多的资源时。
单例模式可以在系统设置全局的访问点,优化环共享资源访问,方便管理和维护。

1.2 单例模式的几种形式

几种常见的单例实现方式:在这里插入图片描述
使用说明:不建议使用第 1 懒汉式种和第 2种饿汉方式。
饿汉式,是线程安全的,执行效率高,但是不能延时加载。
懒汉式,线程安全,调用效率略低,但是可以延时加载。
第三种方法同步的开销同样比较大。双重检测锁注意使用了两个同步块,在某些情况下会出现异常,使用volite要好一些。只有在要明确实现 lazy loading 效果时,才会使用登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。

1.2.1 饿汉式(单例对象立即加载)

要点:
构造器私有化。
静态private的实例。
一个public获得实例的方法。
要点:
构造器私有化。
静态private的实例。
一个public获得实例的方法。

public class SingletonDemo1 {
    private static SingletonDemo1 singletonDemo1 = new SingletonDemo1();

    private SingletonDemo1() {
    }

    public static SingletonDemo1 getInstance() {
        return singletonDemo1;
    }

    public static void main(String[] args) {
        SingletonDemo1 singletonDemo1 = SingletonDemo1.getInstance();

        SingletonDemo1 singletonDemo2 = SingletonDemo1.getInstance();
        System.out.println(singletonDemo1 == singletonDemo2  );
    }
}

说明:static 变量会在类装载时初始化,此时不会涉及到多线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此可以省略synchronized关键字。
问题:如果只是加载本类,而不调用getInstance(),则会造成资源浪费。

1.2.2 懒汉式

公共要点:
私有构造器
成员变量只声明不初始化,静态私有
public static synchronzied 获得实例。
获得实例中如果已经初始化就直接返沪,否则创建。
特点:
实现了单例对象延迟加载;
不会出现饿汉式的资源浪费问题,资源利用率高了。
但是每次获得实例都要先同步,运行效率低。
synchronized方法实现的单例:

public class SingletonDemo2 {
    private static  SingletonDemo2 singletonDemo2;

    private SingletonDemo2() {
    }

    public static synchronized SingletonDemo2 getInstance() {
        if (singletonDemo2 == null) {    
            singletonDemo2 = new SingletonDemo2();
        }
        return singletonDemo2;
    }

    public static void main(String[] args) {
        SingletonDemo2 singletonDemo1 = SingletonDemo2.getInstance();

        SingletonDemo2 singletonDemo2 = SingletonDemo2.getInstance();
        System.out.println(singletonDemo1 == singletonDemo2);
    }
}

这种方式的实现也比较简单,问题是每次获得单例都要进行一次加锁操作,效率低。

1.2.3 双重检测锁方式

将同步内容下放到if内部,提高了执行效率,不用每次获取对象时都进行同步,只有第一次才同步,创建之后就没必要了。
实现方式是使用 volatile 关键字来修饰实例。该关键字的作用是当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存,同时还能防止指令重排序,因为创建个对象至少需要三个指令:
分配内存空间
初始化对象
将对象指向刚分配的内存空间
有些编译器会进行优化,2和3在时间维度上可能反了,这样就无法准确创建预期的实例了。

class SingletonDemo3_2 {
    //防止指令重排序,因为new一个对象时需要很多条指令,可能存在重排序的问题。
    private volatile static SingletonDemo3_2 instance;
    private SingletonDemo3_2() {
    }
    
    public static SingletonDemo3_2 getInstance() {
        //减少拥塞,因为后续判断都要先加锁
        if (instance == null) {
            synchronized (SingletonDemo3_2.class) {
                //判断获得锁之后是否instance仍然为空
                if (instance == null)
                    instance = new SingletonDemo3_2();
            }
        }
        return instance;
    }
}

1.2.4 静态内部类实现懒加载

备线程安全,效率和延时三个好处,原因:
初始化外部类的时候不一定立即初始化静态内部类,只有真正去调getInstance的时候才初始化内部类,从而实现类延时加载。
类的加载是天然线程安全的,因此是线程安全的,也就是JVM帮我们实现了线程安全。
定义为final,所以是只读的,谁都不能修改。
要点:
外部类没有static属性,不会像饿汉式那样立即加载对象。
只有真正调用getInstance(),才会加载静态内部类。加载时是线程安全的。instance是static final类型,保证类内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全。

public class SingletonDemo4 {
    private static int count = 0;
    private SingletonDemo4() {
         synchronized (Singleton.class) {
            if(count > 0){
                throw new RuntimeException("创建了两个实例");
            }
            count++;
        }
    }
   
    public static SingletonDemo4 getInstance() {
        return SingletonDemoInstance.instance;
    }
    
    private static class SingletonDemoInstance {
        private static final SingletonDemo4 instance = new SingletonDemo4();
    }

    private Object readResolve(){
    
    }
}

不足:可以被通过反射或者序列化破坏。
为什么可以被反射破坏:
因为通过setAccessible(true)指令就可以访问到构造方法。
反射能够破坏的不仅仅是静态内部类方式,双重检查锁等方式都会存在问题。
前者需要在构造方法里加判断,或者抛异常。
序列化,也可以破坏除枚举之外的其他单例模式。

后者是再添加一个方法

1.2.5 枚举方式实现

枚举本身就是单例的,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞! 缺点:无延迟加载。

public enum SingletonDemo5 {
    INSTANCE;// 本身就是单例的,但是不能延时加载

    public void singleOpertation(){

    }
}

1.3 几种破解单例模式的方式

枚举都破解不了

1.3.1 反射可以破解上面几种实现方式

public class Test2 {
    public static void main(String[] args) throws Exception {
        SingletonDemo6 s1 = SingletonDemo6.getInstance();
        SingletonDemo6 s2 = SingletonDemo6.getInstance();
        System.out.println(s1);
        System.out.println(s2);

        Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("pattern.singleton.SingletonDemo6");
        Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null);

        c.setAccessible(true);

        SingletonDemo6 s3 = c.newInstance();

        SingletonDemo6 s4 = c.newInstance();
        System.out.println(s3);
        System.out.println(s4);

    }
}

防范措施,修改私有构造函数,如果不为空,则抛出异常

  private SingletonDemo6() {
        if(singletonDemo2!=null)
            throw new RuntimeException();
    }

1.3.2 反序列化破解

FileOutputStream fos = new FileOutputStream("/Users/qingchao1/Desktop/workspace/Java_study/JavaSE/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);
        oos.close();
        fos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/qingchao1/Desktop/workspace/Java_study/JavaSE/a.txt"));
        SingletonDemo6 s3 = (SingletonDemo6) ois.readObject();
        System.out.println(s3);

在单例类中实现一下这个函数:

   private Object readResolve() throws ObjectStreamException {
        return singletonDemo2;
    }

1.4 容器式解决大规模生产单例的问题

public class ContainerSingleton {
    private ContainerSingleton() {

    }

    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object object = null;
                try {
                    object = Class.forName(className).newInstance();
                    ioc.put(className, object);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return object;
            } else {
                return ioc.get(className)
            }
        }
    }
}

2.单例模式在VFS中的应用

VFS 表示虚拟文件系统 ( Virtual File System),它用来查找指定路径下的资源。 VFS 是一个抽象类 , MyBatis 中提供了 JBoss6VFS 和 DefaultVFS 两个 VFS 的实现 ,MyBatis 初始化的流程时,会用到这两个扩展点。
VFS 的核心宇段的含义如下:

public abstract class VFS {
  private static final Log log = LogFactory.getLog(VFS.class);

  /** Singleton instance holder. */
  private static class VFSHolder {
    static final VFS INSTANCE = createVFS();
    }
    }

这个用的是懒汉式方式创建的单例模式,不同的是这里是使用了一个方法创建的,而不是简单的new。看这个方法的代码,会发现是用反射来创建的:

  static VFS createVFS() {
 ...

      // Try each implementation class until a valid one is found
      VFS vfs = null;
      for (int i = 0; vfs == null || !vfs.isValid(); i++) {
        Class<? extends VFS> impl = impls.get(i);
        try {
          vfs = impl.getDeclaredConstructor().newInstance();
          if (!vfs.isValid()) {
            if (log.isDebugEnabled()) {
              log.debug("VFS implementation " + impl.getName() +
                  " is not valid in this environment.");
            }
          }
        } 
        .....
      return vfs;
    }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值