java-设计模式-单例模式

目录

    • 1. 六大设计原则
    • 2. 什么是单例模式?
    • 3. 单例模式的特点?
    • 4. 单例模式VS静态类
    • 5. 单例模式的几种实现方式
      • 1. 懒汉式
      • 2. 饿汉式
      • 3. DCL(double-check-locking)双检锁
      • 4. 静态内部类(推荐)
      • 5. 枚举单例
    • 6. DCL原理
    • 7. 多进程中单例为什么会失效?

1. 六大设计原则

一句话总结个原则特点

  1. 单一职责原则 :一个类只负责一项职责
  2. 里式替换原则:所有应用父类的地方都能够使用子类替代,子类可进行功能扩展而不是重写、覆盖父类方法
  3. 依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖抽象,抽象不应该依赖细节,细节应该依赖抽象,Java中,抽象是指接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范,而不设计具体的操作,把具体的细节交给实现类去完成。
  4. 接口隔离原则:建立单一的接口,不要尽力庞大臃肿的接口,尽量细化接口,接口中的方法尽量的少,即为各个类分别建立专用的接口
  5. 迪米特原则:尽量降低类与类直接的耦合度
  6. 开闭原则:一个类、模块或者函数应该对外扩展开发,对修改关闭,用抽象构建框架,有实现扩展细节

2. 什么是单例模式?

在程序的生命周期中仅有一个实例即单例模式

3. 单例模式的特点?

  • 只有一个实例
  • 构造方法是私有的
  • 单例必须自己创建自己的唯一实例
  • 单例类必须向其他对象提供这一实例

4. 单例模式VS静态类

  • 单例可以继承和被继承,解决方法可以被重写,静态方法不可以
  • 静态方法中产生的对象在执行完后被释放,进而被GC清理,不会一直在内存中
  • 静态类会在第一次运行的时候被初始化,单例模式可以进行延迟加载
  • 单例模式一般是一些重量级的任务,如果经常初始化和释放会占用大量资源,使用到单例模式可以将其常驻内存节约资源,比如Sqlite数据库的连接等等
  • 静态方法访问效率更高

5. 单例模式的几种实现方式

1. 懒汉式

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

根据代码可以看出是具有延迟加载的特性,但是非线程安全

2. 饿汉式

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

线程安全,但是不具有延迟加载的特性,造成资源浪费

3. DCL(double-check-locking)双检锁

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

将饿汉式与懒汉式的优点相结合,synchronized内外都加判断,一是为了不必要的同步导致效率问题,二是保证了线程安全

4. 静态内部类(推荐)

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

第一次加载Singleton时并不会初始化instance,而是在调用getInstance()的时候虚拟机会去加载SingletonHolder类,不仅保证了线程安全,也保证了单例对象的唯一性,同时也延迟了单例的实例化,所以推荐使用

5. 枚举单例

public enum SingletonEnum {
    INSTANCE;
}

简单,而且反序列化后也是单例,其他方式反序列化后会生成多个对象

6. DCL原理

第一层判断是为了避免不必要的同步,第二层判断就比较复杂了:假设线程A执行了instance=DoubleSyncCheckSingleDemo()语句,这个操作其实不非一个原子操作,最终会被编译成多条汇编指令,大致做了3件事情:

  1. 给 DoubleSyncCheckSingleDemo的实例分配内存
  2. 调用 DoubleSyncCheckSingleDemo()的构造函数,初始化成员字段
  3. 将instance对象指向分配的内存空间(此时instance就不是null了)

但是由于Java编译器允许处理器乱序执行,JDK1.5之前JMM(Java Memory Model 即java内存模型)中Cache、寄存器到主内存回写顺序的规定,2、3的执行顺序无法保障,如果先执行了3,2还未执行完毕,这个时候切换了线程B,这个时候instance因为已经在线程A执行了3,instance!=null,所以线程B直接取走了instance,再使用时就会报错了,这就是DCL失效问题,JDK1.5以后使用volatile关键字将解决此问题。

7. 多进程中单例为什么会失效?

因为进程间,内存空间是相互独立的,会产生不同的副本。

你可能感兴趣的:(面试,java,单例模式,设计模式)