-
模式定义
保证一个类只有一个实例,并提供一个全局访问点。
-
单例模式的实现主要有以下四种
一、懒汉模式
public class LazySingleton {
private static volatile LazySingleton instance;
//不允许被实例化
private LazySingleton(){
}
/**
* 获取实例
* @return
*/
public static LazySingleton getInstance() {
if(instance!=null){
synchronized (LazySingleton.class){
if(instance!=null) {
instance = new LazySingleton();
/*一个new操作在字节码层面对应三步操作
* 1、开辟空间
* 2、初始化
* 3、变量赋值
* cpu可能会重排需2、3步。如果先执行了第3步,则会返回一个没有初始化的对象,
* 发生空指针异常。增加volatile关键字,防止重排序
* */
}
}
}
return instance;
}
}
特点:延时加载,只有在正在需要的时候才开始实例化。
1、线程安全问题
2、双重判断double check,加锁优化(锁也可以直接加锁到方法上,但是会耗费性能)
3、使用volatile关键字,防止指令重排序
二、饿汉模式
class HunglySingleton {
private static HunglySingleton instance= new HunglySingleton();
//不允许被实例化
private HunglySingleton(){
}
/**
* 获取实例
* @return
*/
public static HunglySingleton getInstance() {
return instance;
}
}
特点:类加载的初始化阶段就完成了实例的初始化,本质上是借助于jvm的类加载机制,保证实例的唯一性。(急加载)
类加载机制:
1、加载:加载二进制文件到内存中,并生成对应的class数据结构
2、连接:包括验证、准备(给静态变量赋默认值,引用类型为null,int为0,boolean为false等等)、解析(将符号应用替换为直接引用)
3、初始化(执行静态代码块、给静态变量赋初值)
注意:如果当前类的父类没有被加载,则会先加载父类
三、静态内部类模式
class InnerSingleton {
private static class InnerSingletonHolder {
private static InnerSingleton instance= new InnerSingleton();
}
//不允许被实例化
private InnerSingleton(){
}
/**
* 获取实例
* @return
*/
public static InnerSingleton getInstance() {
return InnerSingletonHolder.instance;
}
}
特点:也是借助jvm的类加载机制,保证实例的唯一性(懒加载)
当访问instance时才会初始化实例
四、enum模式
enum EnumSingleton {
INSTANCE;
/**
* 获取实例
* @return
*/
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
特点:简单,线程安全,也能够防护反射攻击(禁止反射实例化enum类型类)。
特殊情况:
一、当使用反射创建实例时还能保证单例吗?
1、懒汉模式:无解
2、饿汉模式
解决方法:把构造方法增加判断,如果使用反射调用私有构造方法,则会抛异常。
//不允许被实例化
private HunglySingleton(){
if(instance!=null){
throw new RuntimeException("单例不允许多例实例");
}
}
3、静态内部类模式
同饿汉模式的解决办法
4、enmu模式
天然的,就不能被反射调用创建实例,当使用反射newInstance()就会抛异常。
二、当使用序列化反序列化创建实例时还能保证单例吗?
当我们对静态内部类模式测试一下,序列化并反序列化后,还是同一个实例吗?
public class InnerSingletonTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
InnerSingleton instance = InnerSingleton.getInstance();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("innerSingletonObject"));
objectOutputStream.writeObject(instance);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("innerSingletonObject"));
InnerSingleton instance2 = (InnerSingleton)objectInputStream.readObject();
objectInputStream.close();
System.out.println(instance==instance2);
}
}
class InnerSingleton implements Serializable {
private static class InnerSingletonHolder {
private static InnerSingleton instance= new InnerSingleton();
}
//不允许被实例化
private InnerSingleton(){
if(InnerSingletonHolder.instance!=null){
throw new RuntimeException("单例不允许多例实例");
}
}
/**
* 获取实例
* @return
*/
public static InnerSingleton getInstance() {
return InnerSingletonHolder.instance;
}
}
答案是false。因为反序列化没有调用InnerSingleton的构造方法。
解决办法:
查看serializable接口的说明如下
需要增加一个readResolve方法
Object readResolve() throws ObjectStreamException{
return getInstance();
}
注意:enum枚举类除外,有单独的处理方法,查看readObject()的源码如下:
-
jdk和spring中单例模式的应用
jdk:Runtime类(饿汉)、Currency
spring:DefaultSingletonBeanRegistry、ReactiveAdapterRegistry、ProxyFactoryBean(aop包)