为了账号安全,请及时绑定邮箱和手机立即绑定

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

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 总结

本来是一个很简单的模式,结合了很多方面也会变得很复杂。
单例模式是简单也常见的模式,所以很容易记住。
因为简单,所以一眼就能认出来。
饿汉懒汉有时候会看到面试题有相关字眼,用即时加载和延迟加载感觉更好记一些,面试经验不丰富,不记得具体题目了。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
全栈工程师
手记
粉丝
2664
获赞与收藏
74

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消