doclist 阅读(106) 评论(0)

前言

设计模式是人们在日常工作中总结出来的一些好的设计方式。用于指导人们能够写出优雅(可扩展,好维护)的代码。

也能让自己心情愉快。

简介

单例模式是一种比较简单的模式。定义为确保某一个类只有一个是实例,而且自行实例化并向整个系统提供这个实例。

例子

饿汉式:

public class Singleton {
    private static final Singleton singeleton = new Singleton();
    // 私有对象可限制new多个对象
    private Singleton(){}
    
    public static Singleton getSingeleton(){
        return singeleton;
    }
    
}

优点:

  1. 线程安全
  2. 实现简单

缺点:

  1. 不是懒加载,在加载类时就会被初始化。即使该类你没有被使用。
  2. 如果实例依赖参数则无法实现

懒汉式 线程不安全(不推荐)

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}

优点:

  1. 实现了懒加载,在单线程下在使用时才能正确创建实例

缺点:

  1. 虽然使用了懒加载,但是有个严重问题。在多个线程并行调用getInstance()时会创建多个实例。这样在这个充满多线程开发的web应用下是很不可取的。所以不推荐这种用法。

懒汉式 线程安全(双重检验锁模式)

使用同步块枷锁的方式来保证线程安全,为何双重判断?当有多个线程同时进入第一个if的时候。如果此时未实例化,则会只有一个线程进入同步代码块,其他代码块将会等待,然后进入第二个。如果该线程在获取锁后已经实例化就跳过实例化,所以存在第二重判断空。
public static Singleton getSingleton() {
    if (instance == null) {                       
        synchronized (Singleton.class) {
            if (instance == null) {              
                instance = new Singleton();
            }
        }
    }
    return instance ;
}

以上代码看着很完美,但是它存在一个问题。instance = new Singleton();就是这个

他并非一个原子操作。这句在jvm中做了三件事情

  1. 分配内存
  2. 调用构造函数初始化成员变量
  3. 分配内存空间(instance !=null)

由于jvm在编译时存在指令重排序的优化,也就是第二步和第三步有可能被交换。这样就会出现先分配空间在初始化。这时如果有线程到了第一个if就会错误的得到instanc!=null的并没有初始化的实例。如果使用没有初始化的实例则会报错。

解决方案为添加volatile关键字来防止指令的重排

优化后的代码:

public class Singleton {
    private volatile static Singleton instance; //声明成 volatile
    private Singleton (){}

    public static Singleton getSingleton() {
        if (instance == null) {                         
            synchronized (Singleton.class) {
                if (instance == null) {       
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
   
}

优点:

  1. 实现了懒加载
  2. 线程安全

缺点:

  1. 代码较复杂而且还隐含jvm问题,不容易理解,一不小心容易犯错
  2. java5以前的版本volatile有缺陷无法避免重排序
  3. 存在同步代码块,性能上可能不如其他方式

懒汉式,静态内部类(懒汉式加载推荐)

《Effective Java》上推荐

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

优点:

  1. 懒加载
  2. 线程安全
  3. 读取实例不同步,性能比双重校验好

缺点:

  1. 基本无缺点,真要说缺点就是不出名

小结

就一般而言,直接使用饿汉式即可,如果要求使用懒加载推荐使用静态内部类。

为何要使用单列模式

优点:

  1. 单例模式在内存中,只有一个实例,减少内存开支。
  2. 当一个对象的产生需要比较多资源时,如读取配置可以通过启动时实现一个单例对象来解决
  3. 避免对资源的多重占用,避免对一个资源文件的同时写操作

缺点:

  1. 没有接口,扩展难
  2. 对测试不理
  3. 与单一职责原则冲突

使用场景

  1. 要求生成巍译序列号的环境
  2. 整个项目需要一个共享访问点或共享数据,如web页面计数器,使用单例可以保持计数器的值
  3. 创建一个对象需要消耗资源过多,如访问IO和数据库