0 简介
单例模式属于创建型模式,是23种设计模式的一种,相对比较简单,因为只有一个类也比较常用,因为只有一个类没有和其他类做交互。
1 定义
保证一个类仅有一个实例,并提供一个对他访问的全局访问点。
2 地图
将他放在gof设计模式中的地图中来看
3 场景
比如一个系统的任务管理器应该只有一个,不管你打开多少个任务管理器窗口,操作的都是一个任务列表。如果每次打开一个窗口就弹出一个新的任务管理器的话那就乱套了。
Spring中的bean就是单例的,Servlet也是单例的。
但是说单例模式严格来讲不太合适,因为单例要求保证一个类只有一个实例,bean和servlet都是可以自己去new的。如果直接使用autowired去注入或者注册Servlet的话,使用的都是同一个实例。
测试的方法比较简单,在servlet中定义一个静态servlet数组,然后每次执行doGet/doPost/service的时候就将this放到数组里,第二次执行servlet的时候数组中就会存有两个servlet对象,用==比较两个对象引用是否相同。相同就是单例的。
4 实现
通常实现方式是将构造器方法设置为私有,返回一个静态对象给客户端。将构造器设置成private使得客户端无法自己去生成一个新的实例。
单例的实现有多个类型,常用的就是饿汉和懒汉,一个是立即加载一个是延迟加载。饿汉是立即加载通常不需要考虑线程安全,懒汉要考虑线程安全。
4.1 饿汉
饿汉分两种,一个是变量直接初始化,一个是走静态代码块,两个是一样的,静态代码块逼格高。
public class HungrySingle{
// 直接初始化
private static HungrySingle single = new HungrySingle();
private HungrySingle(){
}
public static HungrySingle getInstance(){
return single;
}
}
public class HungrySingle{
// 静态代码块
private static HungrySingle single;
static{
single = new HungrySingle();
}
private HungrySingle(){
}
public static HungrySingle getInstance(){
return single;
}
}
优点:
简单易懂,不用考虑线程安全问题
缺点:
即时加载,在class load的时候就进行了初始化,但是如果这个类从头到尾都没有使用的话,就会造成内存浪费,不过这种情况比较少。
类的加载链接初始化
4.2 懒汉
类实例化的时候才创建对象。
4.2.1 非线程安全懒汉
如下代码为非线程安全,在尚未实例化的时候,两个线程都执行到非空判断的时候,判断校验都通过,指令都指向下一条实例化的指令,此时就会实例化出两个对象。此时其中一个对象就会失效。
public class LazySingle{
// 非线程安全
private static LazySingle single;
private LazySingle(){
}
public static LazySingle getInstance(){
if(single == null){
single = new LazySingle();
}
return single;
}
}
优点:
没有(延迟加载勉强算)
缺点:
非线程安全。
4.2.2 线程安全懒汉
public class LazySingle{
// 非线程安全
private static LazySingle single;
private LazySingle(){
}
public static synchronized LazySingle getInstance(){
if(single == null){
single = new LazySingle();
}
return single;
}
}
在上述代码中加了同步修饰符,达到了线程安全的目的,但是效率较慢。因为同一时刻只有一个线程能调用执行这个方法。
优点:
延迟加载,线程安全
缺点:
慢
4.3 DOUBLE-CHECK
public class LazySingle{
private static volatile LazySingle singleton;
private LazySingle() {}
public static LazySingle getInstance() {
if (singleton == null) {
synchronized (LazySingle.class) {
if (singleton == null) {
singleton = new LazySingle();
}
}
}
return singleton;
}
}
这里涉及指令重排和线程安全,要注意变量声明的时候加了volatile修饰符,volatile修饰的变量不会指令重排。
优点:
线程安全,在类不一定实例化的情况下可以节省空间,相对上一个线程安全的懒汉较快
//TODO 后面要写篇数据记录能快多少= =
缺点:
复杂
4.4 静态内部类
public class Single{
private Single(){}
private static class SingleInstance{
private static Single single = new Single();
}
public static Single getInstance(){
return SingleInstance.single;
}
}
静态内部类的好处是在类加载的时候不会实例化,代码也没有double-check那么复杂,同时也保证了线程安全和延迟加载。
优点:
相对DOUBLE-CHECK简单一些,延迟加载,如果类不是经常使用的话可以节省内存空间,线程安全
4.5枚举
public enum SingletonDemo{
INSTANCE;
public void otherMethods(){
System.out.println("Something");
}
}
优点:
代码简单
缺点:
类似饿汉模式没有延迟加载。
5 使用场景
自己写的小型缓存可以使用单例模式。
一些没有成员属性只有成员方法的且常用的类,也适用于单例模式,如servlet。
缓存的单例模式难点通常在于键值对修改时的线程安全问题。
// TODO 这个例子需要较大篇幅,等后面写了再加超链。
6 总结
本来是一个很简单的模式,结合了很多方面也会变得很复杂。
单例模式是简单也常见的模式,所以很容易记住。
因为简单,所以一眼就能认出来。
饿汉懒汉有时候会看到面试题有相关字眼,用即时加载和延迟加载感觉更好记一些,面试经验不丰富,不记得具体题目了。
共同学习,写下你的评论
评论加载中...
作者其他优质文章