文章目录
1. 概述
指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
2. 特点
- 单例类只有一个实例对象
- 该单例对象必须由单例类自行创建
- 单例类对外提供一个访问该单例的全局访问点
3. 优缺点
3.1 优点
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
3.2 缺点
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的代码功能通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
4. 在Java中的应用场景
- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少GC。
- 某类只要求生成一个对象的时候,如一个班中的班长,每个人的身份证号等。
- 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
- 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池。
- 频繁访问数据库或文件的对象。
- 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如Web中的配置对象、数据库的连接池等。
5.结构与实现
5.1 结构
主要角色:
- 单例类:包含一个实例且能自行创建这个实例的类。
- 访问类:使用单例的类
5.2 实现方式-7种
5.2.1 饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
饿汉式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快.这种方式基于类加载机制避免了多线程的同步问题,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有打到懒加载的效果.
5.2.2 懒汉模式(线程不安全)
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉模式声明了一个静态对象,在用户第一次调用时初始化,虽然节约了资源,但第一次加载时需要实例化,反应稍慢一些,而且在多线程下不能正常工作.
5.2.3 懒汉模式(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种写法能够在多线程中很好的工作,但是每次调用getinstance方法时都需要进行同步,造成不必要的同步开销,而且大部分时候我们是用不到同步的,所以不建议用这种模式.通俗来说,这种设计的问题是在Singleton对象已经创建完成后,仍然需要同步,而在这个时候其实是不需要再进行同步了.
5.2.4 双重检查模式(DCL)
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getInstance() {
if (instance== null) {
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return singleton;
}
}
这种写法再getSingleton方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是再singleton等于null的情况下才创建实例.这里的volatile关键字作用主要是给第一次判断用的,一是保证singleton的可见性,当某个线程创建完成singleton对象后别的线程可以第一时间得到消息.二是禁止指令重排序,防止某个线程创建完对象但未初始化先返回了值,这样会导致别的线程在第一次判断时判断为否而直接执行之后的代码,进而有可能导致空指针异常.
5.2.5 静态内部类单例模式
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder并初始化sInstance,这样不仅能确保线程安全也能保证Singleton类的唯一性,所以推荐使用静态内部类单例模式.
5.2.6 枚举单例
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例.
5.2.7 使用容器实现单例模式
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() {
}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static Object getService(String key) {
return objMap.get(key) ;
}
}
用SingletonManager将多种的单例类统一管理,在使用时根据key获取对象对应类型的对象.这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度.
5.2.8 ps
前6种实现在反序列化时会重新创建对象,将一个单例实例对象写到磁盘再读回来,从而获得了一个实例.反序列化操作提供了eradResolve方法,通过这个方法可以控制对象的反序列化.在这六个实现种如果要杜绝单例对象被反序列化时重新生成对象,就必须加入如下方法:
private Object readResolve() throws ObjectStreamException{
return singleton;
}