java设计模式学习笔记5-单例模式

本文介绍单例模式,实现需构造方法私有化、内部创建静态对象并提供获取方法。有饿汉式和懒汉式,多线程时懒汉式加锁性能慢。反序列化和反射会破坏单例模式,而枚举类可避免此问题,若不用枚举,可加 readResolve 方法但有风险。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单例模式作为对象的创建模式,可以确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类;

实现单例模式的三点要求:

    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一个别的什么对象!

单元素的枚举类天生适合实现枚举类!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值