23天设计模式之单例模式

文章简介

《23天设计模式之单例模式》这是我的第二篇博客。在接下来的23天内,我们将23种设计模式都去了解一下。今天我们就来学习最简单的。

在学习设计模式之前我们不可避免要去想为什么要学习这个东西,它是用来干嘛的?

  • 要知道在软件开发中,要实现可维护、可扩展,就必须尽量。
  • 另外,我认为学习设计模式可以潜移默化地对我们的编程思想产生好的影响。其实我们经常都有使用到设计模式,只是没有去学习过便没有意识到,比如!
  • 设计模式主要是基于OOP编程【】提炼的,它基于以下几个原则。

OOP7大原则

  • 开闭原则。对扩展开放,对修改关闭。
  • 里氏替换原则。继承必须确保超类所拥有的性质在子类中仍然成立。(子类在程序中可以替换父类)
  • 依赖倒置原则。要面向接口编程,不要面向实现编程。(实现公共接口,而不是每个接口都实现)
  • 单一职责原则。控制类的粒度大小、将对象解耦、提高其内聚性。(方法的原子性)
  • 接口隔离原则。要为各个类建立他们所需要的专用接口。
  • 迪米特法则。只与你的直接朋友交谈,不跟“陌生人”说话。
  • 合成复用原则。尽量先使用组合或聚合等关联关系来实现,其次才考虑使用继承关系来实现。

设计模式分类

总体分为3大类:

  • 创建型模式:关注点是如何创建对象,其核心思想是要把对象的创建和使用相分离,这样使得两者能相对独立地变换。
    • 工厂方法:Factory Method
    • 抽象工厂:Abstract Factory
    • 建造者:Builder
    • 原型:Prototype
    • 单例:Singleton
  • 结构型模式。把类或对象结合在一起形成一个更大的结构。
    • 适配器
    • 桥接
    • 组合
    • 装饰器
    • 外观
    • 享元
    • 代理
  • 行为型模式。类和对象如何交互,及划分责任和算法。
    • 责任链
    • 命令
    • 解释器
    • 迭代器
    • 中介
    • 备忘录
    • 观察者
    • 状态
    • 策略
    • 模板方法
    • 访问者

单例模式

  • 单例模式指在一个进程中,某个类有且仅有一个实例。
  • 为了防止调用者调用new方法继续构造,构造方法要设置为private。
  • 调用者要怎么获取到单例呢?我们提供一个public static修饰的静态方法给用户返回单例。
  • 单例模式也分为以下几种,在下面详细介绍。

饿汉式单例

  • 所谓饿汉式单例是指,不管是否调用获取实例的方法,都会产生一个单例。直接上代码!
  • 缺点:不管用不用都会产生单例,浪费空间。
  • 也称为静态单例模式。
public class Singleton {

    //静态单例
    private static final Singleton INSTANCE = new Singleton();

    //私有的构造方法
    private Singleton(){};

    // 获取单例的静态方法
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

懒汉式单例

  • 所谓懒汉是指,这个单例很懒,只有在调用方调用时,才去生成单例。
  • 如果在多线程下不加锁的话,会多次调用构造方法创建多个实例。直接上代码。
public class Singleton {

    //调用getInstance()方法时初始化
    private static Singleton instance;

    //输出文字看这个构造方法被调用了几次
    private Singleton(){
        System.out.println(Thread.currentThread().getName() + "调用了构造方法");
    };

    //加了判空
    public static Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    //使用多线程测试构造方法被调用了几次。正常来说应该只调用一次。
    public static void main(String[] args) {
        for (int i = 0; i < 4; i++) {
            new Thread(Singleton::getInstance).start();
        }
    }
}

//输出如下:
Thread-1调用了构造方法
Thread-2调用了构造方法
Thread-0调用了构造方法

分析:

  • 每次运行程序,输出都不一样;构造方法的明显被多次调用,即创建了多个单例。

DCL懒汉式单例

  • DCL是指双重检锁。
public class Singleton {

    //volatile禁止指令重排优化
    private static volatile Singleton instance;

    private Singleton(){
        System.out.println(Thread.currentThread().getName() + "调用了构造方法");
    };

    //DCL双重检锁
    public static Singleton getInstance(){
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 4; i++) {
            new Thread(Singleton::getInstance).start();
        }
    }
}

//输出:
Thread-0调用了构造方法

分析:

  • 无论执行多少次main函数,始终只有一条线程能调用构造函数。

  • 指令重排:编译器、JVM 或者 CPU 都有可能出于优化等目的,对于实际指令执行的顺序进行调整。

  • 为什么要禁止指令重排:多线程环境中线程交替执行,由于编译器指令重排的存在,两个线程使用的变量能否保证一致性是无法确认的,结果无法预测。

静态内部类实现单例

public class Singleton {

    private Singleton(){
    };

    public static Singleton getInstance() {
        return InnerClass.INSTANCE;
    }

    //静态内部类
    public static class InnerClass {
        private static final Singleton INSTANCE = new Singleton();
    }
}

枚举类实现单例

  • 正所谓道高一尺,魔高一丈。即使我们私有了构造器,单例也并不安全。万能的,因此我们需要。
  • 枚举单例不会被反射破坏。
public enum Singleton {

    INSTANCE;

    public Singleton getInstance(){
        return INSTANCE;
    }
}

以上就是单例模式的主要内容。接下来我们了解以下单例模式的常见应用场景。

单例模式的应用场景

  • Windows的Task Manager(任务管理器)就是很典型的单例模式

  • windows的Recycle Bin(回收站)就是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

  • 网站的计数器,一般也是采用单例模式实现,否则难以同步。

  • 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

以上

感谢您花时间阅读我的博客,以上就是我对单例模式的一些理解,若有不对之处,还望指正,期待与您交流。

本篇博文系原创,仅用于个人学习,转载请注明出处。

04-14 05:34