设计模式-创建型-单例

70 阅读5分钟

特性

  • 设计模式特性:
  • 可维护性:不引入新的bug情况下,快速新增和修改。
  • 灵活性:能快速集成到代码中。
  • 可拓展性:对修改关闭,对拓展开放。
  • 可读性:易于别人理解。
  • 可复用性:可减少代码编写。
  • 简洁性:简单易懂。
  • 可测试性。

设计原则:

单一职责:设计的内容粒度小,一个操作控制一个动作(一个接口控制一种实现) 判断标准:属性量的多少;依赖类的数量(只对需要的做依赖);私有方法量;方法尽量不要只对其中几个属性做操作

开闭原则⭐:定义的内容是模板,实现时不能更改模板,而是在模板上做拓展。

里氏替换:在多态背景下,接口/方法能接收的参数可以为所有实现了的类;能够根据接口知道相关实现类要做什么。

接口隔离:接口中的方法根据重要性做分离,避免实现时不必要的功能完成;通过实现多个接口完成实际操作。 减少代码冗余;减少粒度,体现层次;

依赖倒置:提供中间层抽象类,上层与下层内容依赖中间层。手机为上层,电话卡槽为抽象层,电话卡可替换为下层。 做到解耦

迪米特:有关系的类采用合适的内容做联系,没有关系的老死不相往来。

设计模式是设计原则的实现。

创建型

单例模式

单例模式的运用非常广泛。目的为了保证只有一个实例向外提供服务。

单例模式分为【饿汉】与【懒汉】两种方式。

饿汉模式

class HSingleton {
    private static HSingleton instance = new HSingleton();
    
    private HSingleton{}
    
    public static HSingleton getInstance(){
        return instance;
    }
}

饿汉模式虽然能保证实例的创建,在JVM创建对象后该实例就能创建成功,虽然能提高获取速度,但是会拉慢启动速度,且长时间未使用时存在内存空间浪费。

懒汉模式 - SIMPLE

为了完善饿汉模式中未提供延迟加载,懒汉模式中增加在调用时才去创建实例。

class LSingleton{
    priavte static LSingleton instance = null;
    
    private LSingleton(){
        
    }
    
    public static LSingleton getInstance(){
        if(instance == null){
            instance = new LSingleton();
        }
        return instance;
    }
}

通过if(instance == null) 判读后去实例化,调用时发现未实例化后才去实例化对象。但是在并发环境下,多个线程进入后判断后没有控制直接进入,初始化过程中,instance都未完成实例,造成对象多次实例。

最后出现 obj1 == obj2 为 false

懒汉模式 - SYNC

为了保证并发下,每次只有一个线程调用到getInstance()并进行实例化,其他线程进入时将阻塞一直等待。

class LSingleton{
    priavte static LSingleton instance = null;
    
    private LSingleton(){
        
    }
    
    public static synchronized LSingleton getInstance(){
        if(instance == null){
            instance = new LSingleton();
        }
        return instance;
    }
}

锁加载方法上,粒度很大,导致函数并发能力很低,相当于串行化执行,并发情况下也不是可取方案。

懒汉模式 - DCL

class LSingleton{
    priavte volatile static LSingleton instance = null;
    
    private LSingleton(){
        
    }
    
    public static LSingleton getInstance(){
        if(instance == null){
            synchronized(){
                if(instance == null){
                    instance = new LSingleton();
                }
            }
        }
        return instance;
    }
}

DCL:DOUBLE CHECK LOCK(双检锁)

volatile:JVM中做实例化过程调用时。1、保证修改后被其他线程可见;2、多线程中保证代码执行后的结果,不会出现指令重排

指令重排发生:instance = new LSingleton() 在JVM中做了以下三件事

给instance 分配内存空间

调用instance构造方法,初始化instance

instance 指向分配好的内存空间地址。

实际在分配了内存空间后,可能先初始化,也可能先指向内存地址。

如果先执行分配好的内存地址,instance就不为空了,其他线程等到后使用发现为null,从而发生异常。

静态内部类

静态内部类的特性能保证外部影响不到内部的调用。在调用getInstance()方法时,方法中去获取静态内部类中的实例。而JVM对于静态内部类,只会初始化一次。

class Singleton{
    private static class SingletonHandler{
        private static Singleton instance = new Singleton();
    }
    private Singleton(){}
    
    public static Singleton getInstance(){
        return SingletonHandler.instance;
    }
}

基于反射创建

单例模式中实例的创建由私有方法控制,而反射想要创建实例,则要破坏属性的私有性。

public static void main(String[] args) {

        try {
            
            //反射中,欲获取一个类或者调用某个类的方法,首先要获取到该类的Class 对象。
            Class<Singleton> clazz = Singleton.class;

            //getDeclaredXxx: 不受权限控制的获取类的成员.
            Constructor c = clazz.getDeclaredConstructor(null);

            //设置为true,就可以对类中的私有成员进行操作了
            c.setAccessible(true);

            Object instance1 = c.newInstance();
            Object instance2 = c.newInstance();

            System.out.println(instance1 == instance2); // 返回为false
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

为了控制只有一个实例生成,在构造方法中添加对实例的判断,若为null则抛出异常。

    private Singleton(){
        if(SingletonHandler.instance != null){
            throw new RuntimeException("不能多次创建!");
        }
    }

这样操作后,我只想在做创建操作,结果又多了抛出异常,影响该功能的简洁性。

序列化

序列化本身不能保证一个类的对象只有一个,但是可以通过字节流写出在读取确保单例。

class Singleton implements Serializable {

    private volatile static Singleton singleton;

    private Singleton() {

    }

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

}

@Test
public void test() throws Exception{

    //序列化对象输出流
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file.obj"));
    oos.writeObject(Singleton.getInstance());

    //序列化对象输入流
    File file = new File("file.obj");
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    Singleton Singleton = (Singleton) ois.readObject();

    System.out.println(Singleton);
    System.out.println(Singleton.getInstance());

    //判断是否是同一个对象
    System.out.println(Singleton.getInstance() == Singleton);//false

}

是不是还没解决,发现还是false。

这是因为序列化与反序列化破坏了单例性。

那是谁破坏了单例性呢?

通过方法可知,在Singleton Singleton = (Singleton) ois.readObject();中,按照下面链路可追踪明白

// readObject --> readObject0 --> checkResolve(readOrdinaryObject(unshared)) --> readOrdinaryObject
// --> obj = desc.isInstantiable() ? desc.newInstance() : null;

追踪到最后一个方法后,你会发现

try {
    // isInstantiable:是否为serializable类,则可在运行时被实例化。这里为true
    // newInstance 最终还是通过反射创建的对象,所以
    obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
    throw (IOException) new InvalidClassException(
        desc.forClass().getName(),
        "unable to create instance").initCause(ex);
}