设计模式——单例模式

设计模式——单例模式

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

实际应用

在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。

  • 一个打印机可以链接两个电脑,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。
  • 每台计算机可以有若干通信端口,避免一个通信端口同时被两个请求同时调用。
  • 一台计算机可以用多个进程或线程,不能让多个线程或者进程对同一个应用文件进行修改和访问

单例模式的特点

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

单例模式的优点

1. 节约系统资源:由于单例模式只允许创建一个实例,可以避免多次创建对象导致资源的浪费。
2. 方便资源共享:可以使用单例模式来管理全局变量或共享资源,方便数据共享和通信。
3. 全局访问点:通过单例模式可以提供一个全局的访问点,方便对对象的访问和操作。

单例模式的缺点

1. 破坏了单一职责原则:单例类的职责过重,既要负责自身的功能,又要负责管理自己的生命周期。
2. 难以扩展:由于单例类的实例被固定,难以扩展和修改。
3. 可能引起并发安全问题:多线程环境下,如果不加锁或者其他保护机制,可能会引起并发安全问题。

单例模式的几种实现方式

1、饿汉式(静态常量)

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

package com.example.singletontest;

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {
    }
    public static Singleton getInstance() {
        return instance;
    }
}
2、饿汉式(静态代码块)

这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。

package com.example.singletontest;

/**
 * 饿汉式单例模式(静态代码块)
 */
public class Singleton {
    private static Singleton instance;
    
    static {
        instance = new Singleton();
    }
    private Singleton() {
    }
    public static Singleton getInstance() {
        return instance;
    }
}
3.懒汉式 (线程不安全)

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

/**
 * 懒汉式单例模式,线程不安全
 */
public class Singleton {
    private static Singleton instance;
    private Singleton (){
        System.out.println("构造方法");
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
 4.懒汉式 (线程安全)

这种方式解决上面第三种实现方式的线程不安全问题,具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。

/**
 * 懒汉式单例模式,线程安全
 */
public class Singleton {
    private static Singleton instance;
    private Singleton (){
        System.out.println("构造方法");
    }
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
5、双检锁/双重校验锁(DCL,即 double-checked locking) 

进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。

这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。

优点:
1. 延迟加载:只有在需要时才会创建实例,避免了提前创建实例导致的资源浪费。
2. 节约性能:只有在第一次创建实例时才进行同步操作,避免了多次重复同步导致的性能损耗。
3. 线程安全:通过双重检查,在多线程环境下可以保证单例对象的创建过程线程安全。

缺点:
1. 可能会出现指令重排序问题:在一些特定的平台或编译器优化情况下,可能会出现指令重排序,导致双重检查失效。
2. 实现复杂:双重检查锁需要进行双重检查,实现起来比较复杂,容易出错。
3. 不适用于旧版Java:在旧版Java中,由于内存模型的问题,双重检查锁存在线程安全性隐患,需要使用volatile关键字进行修饰。

/**
 * 懒汉式单例模式,双重检查
 */
public class Singleton {
    private volatile static Singleton singleton;
    private Singleton (){
        System.out.println("构造方法");
    }
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
6、登记式/静态内部类 

通过一个静态内部类来持有单例实例。在这种方式中,单例实例是在静态内部类被加载时进行初始化,实现了延迟加载和线程安全。

优点:
1. 线程安全:静态内部类只会被加载一次,保证了单例实例的线程安全性。
2. 延迟加载:单例实例的初始化是在静态内部类被加载时进行的,实现了延迟加载,节约了资源。
3. 简洁优雅:相比其他单例实现方式,静态内部类实现方式简洁明了,易于理解和维护。

缺点:
1. 无法传递参数:静态内部类的加载是在getInstance()方法中进行的,无法向构造函数传递参数,不适用于需要传入参数的场景。
2. 依赖JVM类加载机制:静态内部类的实现依赖JVM类加载机制,需要保证类加载时的线程安全性。
3. 无法防止反射攻击:静态内部类的实现方式无法防止反射攻击,可以通过反射调用私有构造函数来创建多个实例。 

/**
 * 懒汉式单例模式,内部类
 */
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){
        System.out.println("构造方法");
    }
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
7、枚举 

枚举单例模式是一种使用枚举类型来实现单例模式的方式。在枚举中定义一个枚举常量INSTANCE,该枚举常量就是单例实例。枚举类型在Java中保证在任何情况下都只会被实例化一次,因此枚举单例模式是天然的线程安全的。

枚举单例模式的实现非常简单,只需要定义一个枚举类,其中只包含一个实例,且该实例在类加载时初始化。枚举单例模式避免了多线程同步的问题,同时也能防止反射和序列化的问题。

要使用枚举单例模式,只需要通过Singleton.INSTANCE来访问单例实例,无需实现复杂的单例模式逻辑。此外,枚举类型还具有序列化机制,可防止反序列化重新创建新的实例。

优点:
1. 线程安全:枚举类的实例由JVM保证只会被实例化一次,在任何情况下都是单例的,因此线程安全。
2. 简洁明了:枚举单例模式实现简单,直观,易于理解和维护。
3. 可防止反射攻击和序列化问题:枚举类型的实例在JVM中只会有一个,防止了反射攻击,而且枚举类默认实现了Serializable接口,可以防止序列化问题。

缺点:
1. 无法懒加载:枚举单例模式在类加载时就会初始化实例,无法实现延迟加载,会造成一定的资源浪费。
2. 无法传递参数:枚举类型不支持传递参数给构造函数,因此无法灵活地创建实例。
3. 限制较多:枚举类型不能被继承、不能被打破等,有一定的限制。

/**
 * 枚举单例模式
 */
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
}

相关推荐

  1. 设计模式

    2024-04-14 08:36:02       63 阅读
  2. 设计模式

    2024-04-14 08:36:02       43 阅读
  3. 设计模式

    2024-04-14 08:36:02       40 阅读
  4. 设计模式

    2024-04-14 08:36:02       39 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-14 08:36:02       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-14 08:36:02       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-14 08:36:02       87 阅读
  4. Python语言-面向对象

    2024-04-14 08:36:02       96 阅读

热门阅读

  1. FPGA设计之Test bench介绍

    2024-04-14 08:36:02       42 阅读
  2. xpinyin,是我们最喜欢python库

    2024-04-14 08:36:02       42 阅读
  3. python:算法竞赛入门之一

    2024-04-14 08:36:02       44 阅读
  4. skynet 使用protobuf

    2024-04-14 08:36:02       42 阅读
  5. 每日一题:C语言经典例题之矩阵对角线元素之和

    2024-04-14 08:36:02       48 阅读
  6. 【数据库】MVCC

    2024-04-14 08:36:02       40 阅读
  7. VPS服务器的优势有哪些?

    2024-04-14 08:36:02       83 阅读
  8. c# Paxos算法实现

    2024-04-14 08:36:02       41 阅读
  9. Pytorch深度学习完整GPU图像分类代码

    2024-04-14 08:36:02       38 阅读
  10. 密码学基础--搞清RFC和PKCS(2)

    2024-04-14 08:36:02       37 阅读
  11. Kotlin关键字三——fun与方法

    2024-04-14 08:36:02       37 阅读
  12. 图像哈希:QSVD

    2024-04-14 08:36:02       33 阅读