单例模式作为对象的创建模式,可以确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类;
实现单例模式的三点要求:
1. 构造方法私有化;
2. 在类的内部创建静态对象并保存;
3. 提供一个获取单例对象的方法;
1.饿汉式单例模式-空间换时间,对象在类加载时就已创建
/**
* 单例模式-饿汉式
*/
public class HungerSingleton {
/**
* 在类的内部创建对象并保存
*/
private volatile static HungerSingleton singleton = new HungerSingleton();
/**
* 私有化构造方法
*/
private HungerSingleton() {
}
/**
* 提供一个获取单例对象的方法
*/
public static HungerSingleton getInstance() {
return singleton;
}
}
2.懒汉式单例模式-时间换空间,第一次使用时才创建对象
/**
* 单例模式-饿汉式
*/
public class LazySingleton {
/**
* 在类的内部创建对象并保存
*/
private volatile static LazySingleton singleton = null;
/**
* 私有化构造方法
*/
private LazySingleton() {
}
/**
* 提供一个获取单例对象的方法
*/
public static synchronized LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
3.多线程时,懒汉式单例模式的获取方法加锁保证只有一个对象,性能慢,每次都要判断!
修改获取单例对象的方法---使用双重检查加锁---只在第一次获取对象时进入同步代码块,后来就不用了,速度更快!
public class LazySingleton {
/**
* 在类的内部创建对象并保存, 针对多线程加volatile关键字,保证有序性和可见性
*/
private volatile static LazySingleton singleton = null;
/**
* 私有化构造方法
*/
private LazySingleton() { }
/**
* 提供一个获取单例对象的方法,双重检查DCL(Double Check Lock),
* 因为 singleton = new LazySingleton(); 新建一个对象内部分为三步:
* 1. 分配内存;
* 2. 初始化对象即属性赋值;
* 3. 对象的内存地址赋值给singleton
*/
public static LazySingleton getInstance() {
if (singleton == null) {
synchronized (LazySingleton2.class) {
if (singleton == null) {
singleton = new LazySingleton();
}
}
}
return singleton;
}
}
4.创建对象的方式: new, clone, 反序列化, 反射;
单例模式的核心: 实现单例模式的类只有一个实例对象! 可能被破坏吗?
4.1 单例模式的构造函数已经私有化,不能再new一个新的对象;
4.2 clone方法虽然是Object的方法,但是,如果类没有实现cloneable接口,直接调用就会报错,就不能克隆复制一份内存区域;
4.3 一个对象序列化为字节流后,再反序列化时,会创建一个新的对象!(enum除外!)
4.4 "反射可以打破一切封装!" 哪怕构造方法私有化,也可setAccessible(true)来获得可用的构造方法,创建新的对象!
所以: 反序列化和反射,会破坏单例模式!
5. 使用枚举类实现单例模式
5.1 所谓枚举类Enum,就是继承了Enum的子类,枚举类可以声明多个对象,没有对象可以有多个属性,但是,每个枚举对象,都有两个固定的属性:name和ordinal,String name是声明的枚举对象的名字,int ordinal是声明的枚举对象的顺序,从0开始;
5.2 java规范中,对于枚举的序列化和反序列化做了特殊的规定,Enum类在序列化和反序列化时有自己的特殊机制:
序列化时,只把name序列化, 反序列化时通过java.lang.Enum的ValueOf()方法根据name去查找枚举类对象,不会创建新的对象!
5.3 枚举类,通过反射获取构造方法想要创建对象时会直接抛出异常,Contractor类的源码中,创建对象之前,对于类的类型进行了判断,如果是Enum类,则: throw new IllegalArgumentException("cannot reflectively create enum Objcects"),不会继续创建新对象!
6. 如果不使用枚举类实现单例模式,又想要保住反序列化不定义新对象,可以再类中加个readResolve方法,在反序列化后,新建对象的readResolve方法会被调用,该方法返回的对象引用将会取代反序列新建的那个对象引用;
private Object readResolve(){
return instance;
}
但是,可能出现"盗用者",修改readResolve方法,return一个别的什么对象!
单元素的枚举类天生适合实现枚举类!