单例模式属于创建型模式,是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 这个例子需要较大篇幅,等后面写了再加超链。
本来是一个很简单的模式,结合了很多方面也会变得很复杂。
单例模式是简单也常见的模式,所以很容易记住。
因为简单,所以一眼就能认出来。
饿汉懒汉有时候会看到面试题有相关字眼,用即时加载和延迟加载感觉更好记一些,面试经验不丰富,不记得具体题目了。