只要一个就够——单例模式

193 阅读4分钟

  单例模式属于创建型模式,是23种设计模式的一种,相对比较简单,因为只有一个类也比较常用,因为只有一个类没有和其他类做交互。

  保证一个类仅有一个实例,并提供一个对他访问的全局访问点。

  

图片描述


  

图片描述


  将他放在gof设计模式中的地图中来看

  

图片描述


  比如一个系统的任务管理器应该只有一个,不管你打开多少个任务管理器窗口,操作的都是一个任务列表。如果每次打开一个窗口就弹出一个新的任务管理器的话那就乱套了。

  Spring中的bean就是单例的,Servlet也是单例的。

  但是说单例模式严格来讲不太合适,因为单例要求保证一个类只有一个实例,bean和servlet都是可以自己去new的。如果直接使用autowired去注入或者注册Servlet的话,使用的都是同一个实例。

  测试的方法比较简单,在servlet中定义一个静态servlet数组,然后每次执行doGet/doPost/service的时候就将this放到数组里,第二次执行servlet的时候数组中就会存有两个servlet对象,用==比较两个对象引用是否相同。相同就是单例的。

  通常实现方式是将构造器方法设置为私有,返回一个静态对象给客户端。将构造器设置成private使得客户端无法自己去生成一个新的实例。

  单例的实现有多个类型,常用的就是饿汉和懒汉,一个是立即加载一个是延迟加载。饿汉是立即加载通常不需要考虑线程安全,懒汉要考虑线程安全。

  饿汉分两种,一个是变量直接初始化,一个是走静态代码块,两个是一样的,静态代码块逼格高。

  优点:

  简单易懂,不用考虑线程安全问题

  缺点:

  即时加载,在class load的时候就进行了初始化,但是如果这个类从头到尾都没有使用的话,就会造成内存浪费,不过这种情况比较少。

  类的加载链接初始化

  类实例化的时候才创建对象。

  如下代码为非线程安全,在尚未实例化的时候,两个线程都执行到非空判断的时候,判断校验都通过,指令都指向下一条实例化的指令,此时就会实例化出两个对象。此时其中一个对象就会失效。

  优点:

  没有(延迟加载勉强算)

  缺点:

  非线程安全。

  在上述代码中加了同步修饰符,达到了线程安全的目的,但是效率较慢。因为同一时刻只有一个线程能调用执行这个方法。

  优点:

  延迟加载,线程安全

  缺点:

  慢

  这里涉及指令重排和线程安全,要注意变量声明的时候加了volatile修饰符,volatile修饰的变量不会指令重排。

  优点:

  线程安全,在类不一定实例化的情况下可以节省空间,相对上一个线程安全的懒汉较快

  //TODO 后面要写篇数据记录能快多少= =

  缺点:

  复杂

  静态内部类的好处是在类加载的时候不会实例化,代码也没有double-check那么复杂,同时也保证了线程安全和延迟加载。

  优点:

  相对DOUBLE-CHECK简单一些,延迟加载,如果类不是经常使用的话可以节省内存空间,线程安全

  优点:

  代码简单

  缺点:

  类似饿汉模式没有延迟加载。

  自己写的小型缓存可以使用单例模式。

  一些没有成员属性只有成员方法的且常用的类,也适用于单例模式,如servlet。

  缓存的单例模式难点通常在于键值对修改时的线程安全问题。

  // TODO 这个例子需要较大篇幅,等后面写了再加超链。

  本来是一个很简单的模式,结合了很多方面也会变得很复杂。

  单例模式是简单也常见的模式,所以很容易记住。

  因为简单,所以一眼就能认出来。

  饿汉懒汉有时候会看到面试题有相关字眼,用即时加载和延迟加载感觉更好记一些,面试经验不丰富,不记得具体题目了。