设计模式之单例模式
前言
设计模式是人们在日常工作中总结出来的一些好的设计方式。用于指导人们能够写出优雅(可扩展,好维护)的代码。
也能让自己心情愉快。
简介
单例模式是一种比较简单的模式。定义为确保某一个类只有一个是实例,而且自行实例化并向整个系统提供这个实例。
例子
饿汉式:
public class Singleton {
private static final Singleton singeleton = new Singleton();
// 私有对象可限制new多个对象
private Singleton(){}
public static Singleton getSingeleton(){
return singeleton;
}
}
优点:
- 线程安全
- 实现简单
缺点:
- 不是懒加载,在加载类时就会被初始化。即使该类你没有被使用。
- 如果实例依赖参数则无法实现
懒汉式 线程不安全(不推荐)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:
- 实现了懒加载,在单线程下在使用时才能正确创建实例
缺点:
- 虽然使用了懒加载,但是有个严重问题。在多个线程并行调用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中做了三件事情
- 分配内存
- 调用构造函数初始化成员变量
- 分配内存空间(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;
}
}
优点:
- 实现了懒加载
- 线程安全
缺点:
- 代码较复杂而且还隐含jvm问题,不容易理解,一不小心容易犯错
- java5以前的版本
volatile
有缺陷无法避免重排序 - 存在同步代码块,性能上可能不如其他方式
懒汉式,静态内部类(懒汉式加载推荐)
《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;
}
}
优点:
- 懒加载
- 线程安全
- 读取实例不同步,性能比双重校验好
缺点:
- 基本无缺点,真要说缺点就是不出名
小结
就一般而言,直接使用饿汉式即可,如果要求使用懒加载推荐使用静态内部类。
为何要使用单列模式
优点:
- 单例模式在内存中,只有一个实例,减少内存开支。
- 当一个对象的产生需要比较多资源时,如读取配置可以通过启动时实现一个单例对象来解决
- 避免对资源的多重占用,避免对一个资源文件的同时写操作
缺点:
- 没有接口,扩展难
- 对测试不理
- 与单一职责原则冲突
使用场景
- 要求生成巍译序列号的环境
- 整个项目需要一个共享访问点或共享数据,如web页面计数器,使用单例可以保持计数器的值
- 创建一个对象需要消耗资源过多,如访问IO和数据库