设计模式-单例模式Singleton

这篇具有很好参考价值的文章主要介绍了设计模式-单例模式Singleton。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

单例模式 (Singleton) (重点)

一个类只允许创建一个对象(或者实例),那这个类就是一个单例类

1) 为什么要使用单例

1.表示全局唯一

如果有些数据在系统中应该且只能保存一份,那就应该设计为单例类:

  • 配置类:在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,应该被映射为一个唯一的【配置实例】
  • 全局计数器:我们使用一个全局的计数器进行数据统计、生成全局递增ID等功能。若计数器不唯一,很有可能产生统计无效,ID重复等

2.处理资源访问冲突

如果使用单个实例输出日志,锁【this】即可。

如果要保证JVM级别防止日志文件访问冲突,锁【class】即可。

如果要保证集群服务级别的防止日志文件访问冲突,加分布式锁即可

2) 如何实现一个单例

常见的单例设计模式,有如下五种写法,在编写单例代码的时候要注意以下几点:

  • 1.构造器需要私有化
  • 2.暴露一个公共的获取单例对象的接口
  • 3.是否支持懒加载(延迟加载)
  • 4.是否线程安全

2.a) 饿汉式

在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的

/**
 * 饿汉式单例的实现
 *  - 不支持懒加载
 *  - jvm保证线程安全
 */
public class EagerSingleton {

    /**
     * 当启动程序的时候,就创建这个实例
     */

    // 1.持有一个jvm全局唯一的实例
    private static final EagerSingleton instance = new EagerSingleton();

    // 2.为了避免别人随意的创建,需要私有化构造器
    private EagerSingleton() {
    }

    // 3.暴露一个方法,用来获取实例
    public static EagerSingleton getInstance() {
        return instance;
    }
}

2.b) 懒汉式

懒汉式相对于饿汉式的优势是支持延迟加载,具体的代码实现如下所示:

支持延迟加载

/**
 * 懒汉式单例的实现
 *  - 支持懒加载
 */
public class LazySingleton {
    
    /**
     * 当需要使用这个实例的时候,再创建这个实例
     */

    // 1.持有一个jvm全局唯一的实例
    private static LazySingleton instance;

    // 2.为了避免别人随意的创建,需要私有化构造器
    private LazySingleton() {
    }

    // 3.暴露一个方法,用来获取实例
    // - 懒加载-线程不安全,因为当面对大量并发请求时,有可能会有超过一个线程同时执行此方法,是无法保证其单例的特点
    // - 加锁:使用 synchronized,(对.class加锁) 但是方法上加锁会极大的降低获取单例对象的并发度
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

2.c) 双重检查锁

饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测锁:

/**
 * 双重检查锁单例的实现
 */
public class DoubleCheckLockSingleton {

    // 1.持有一个jvm全局唯一的实例
    // - 因为创建对象不是一个原子性操作,即使使用双重检查锁,也可能在创建过程中产生半初始化状态
    // - volatile 1.保证内存可见 2.保存有序性
    // - jdk1.9以上,不加volatile也可以,jvm内部处理有序性
    private static volatile DoubleCheckLockSingleton instance;

    // 2.为了避免别人随意的创建,需要私有化构造器
    private DoubleCheckLockSingleton() {
    }

    // 3.暴露一个方法,用来获取实例
    // - 第一次创建需要上锁,一旦创建完成,就不再需要上锁
    // - 事实上获取单例并没有线程安全的问题
    public static DoubleCheckLockSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckLockSingleton.class) {
                // 创建
                if (instance == null) {
                    instance = new DoubleCheckLockSingleton();
                }
            }
        }
        return instance;
    }
}

2.d) 静态内部类

比双重检测更加简单的实现方法,那就是利用 Java 的静态内部类。它有点类似饿汉式,但又能做到了延迟加载。

当外部类 InnerSingleton()被加载的时候,并不会创建 InnerSingleton的实例对象。只有当调用 getInstance() 方法时,InnerSingletonHolder 才会被加载,这个时候才会创建 instance实例。

/**
 * 静态内部类的方式实现单例
 */
public class InnerSingleton {

    // 1.私有化构造器
    private InnerSingleton() {
    }

    // 2.提供一个方法,获取单例对象
    public InnerSingleton getInstance() {
        return InnerSingletonHolder.instance;
    }

    // 3.定义内部类,来持有实例
    // - 特性:类加载的时机 --> 一个类会在第一次使用的时候被加载
    // - 实例会在内部类加载(调用getInstance()方法之后)会创建
    private static class InnerSingletonHolder {
        private static final InnerSingleton instance = new InnerSingleton();
    }

}

2.e) 枚举类

基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

/**
 * 枚举:累加器
 */
public enum GlobalCounter {

    // 这个INSTANCE是一个单例
    // 对于枚举类。任何一个枚举项就是一个单例
    // 本质上就是 static final GlobalCounter instance = new GlobalCounter()
    INSTANCE;
    private AtomicLong atomicLong = new AtomicLong(0);

    public Long getNumber() {
        return atomicLong.getAndIncrement();
    }
}

2.f) 反射入侵

事实上,我们想要阻止其他人构造实例仅仅私有化构造器还是不够的,因为我们还可以使用反射获取私有构造器进行构造,当然使用枚举的方式是可以解决这个问题的,对于其他的书写方案,我们通过下边的方式解决:

// 反射代码
Class<DoubleCheckLockSingleton> instance = DoubleCheckLockSingleton.class;
Constructor<DoubleCheckLockSingleton> constructor = instance.getDeclaredConstructor();
constructor.setAccessible(true);

boolean flag = DoubleCheckLockSingleton.getInstance() == constructor.newInstance();
log.info("flag -> {}",flag);
/**
 * 单例的防止反射入侵的代码实现
 */
public class ReflectSingleton {

    /**
     * 可以使用反射获取私有构造器进行构造
     */
    
    private static volatile ReflectSingleton instance;

    // 为了避免别人随意的创建,需要私有化构造器
    private ReflectSingleton() {
        // 升级版本 --> 不要让人使用反射创建
        if (instance != null) {
            throw new RuntimeException("该对象是单例,无法创建多个");
        }
    }

    public static ReflectSingleton getInstance() {
        if (instance == null) {
            synchronized (ReflectSingleton.class) {
                // 创建
                if (instance == null) {
                    instance = new ReflectSingleton();
                }
            }
        }
        return instance;
    }
}

2.g) 序列化与反序列化安全

事实上,到目前为止,我们的单例依然是有漏洞的

/**
 * 通过序列化
 */
@Test
public void testSerialize() throws Exception {
    // 获取单例并序列化
    SerializableSingleton instance = SerializableSingleton.getInstance();
    FileOutputStream fout = new FileOutputStream("F://singleton.txt");
    ObjectOutputStream out = new ObjectOutputStream(fout);
    out.writeObject(instance);
    // 将实例反序列化出来
    FileInputStream fin = new FileInputStream("F://singleton.txt");
    ObjectInputStream in = new ObjectInputStream(fin);
    Object o = in.readObject();
    log.info("是同一个实例吗 {}", o == instance); // 是同一个实例吗 false
}

在进行反序列化时,会尝试执行readResolve方法,并将返回值作为反序列化的结果,而不会克隆一个新的实例,保证jvm中仅仅有一个实例存在

public class Singleton implements Serializable {
    // 省略其他的内容
    public static Singleton getInstance() {
    }
    // 需要加这么一个方法
    public Object readResolve(){
    	return singleton;
    }
}

3) 单例存在的问题

在项目中使用单例,都是用它来表示一些全局唯一类,比如配置信息类、连接池类、ID 生成器类。单例模式书写简洁、使用方便,在代码中,我们不需要创建对象。但是,这种使用方法有点类似硬编码(hard code),会带来诸多问题,所以我们一般会使用spring的单例容器作为替代方案。

3.a) 无法支持面向对象编程

OOP 的三大特性是封装、继承、多态。单例将构造私有化,直接导致的结果就是,他无法成为其他类的父类,这就相当于直接放弃了继承和多态的特性,也就相当于损失了可以应对未来需求变化的扩展性,以后一旦有扩展需求,比如写一个类似的具有绝大部分相同功能的单例,我们不得不新建一个十分【雷同】的单例。文章来源地址https://www.toymoban.com/news/detail-690500.html

到了这里,关于设计模式-单例模式Singleton的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《golang设计模式》第一部分·创建型模式-01-单例模式(Singleton)

    指目标类(Class)只有一个实例对象(Object),并且向使用该对象的客户端提供访问单例的全局方法。 保证类只有一个实例 有方法能让外部访问到该实例 懒汉式 在第一次调用单例对象时创建该对象,这样可以避免不必要的资源浪费 饿汉式 在程序启动时就创建单例对象,这

    2024年02月14日
    浏览(37)
  • Singleton 单例模式简介与 C# 示例【创建型】【设计模式来了】

    一句话解释:   单一的类,只能自己来创建唯一的一个对象。 单例模式(Singleton Pattern)是日常开发中最简单的设计模式之一。这种类型的设计模式属于 创建型模式 ,它提供了一种创建对象的最佳方式。 这种模式涉及到一个 单一的类 ,该类负责 创建自己的对象 ,同时

    2024年02月06日
    浏览(29)
  • Java设计模式【单例模式】

    单例模式(Singleton Pattern)是一种创建型设计模式,其主要目的是确保一个类只有一个实例,并提供对该实例的唯一访问点。 优点 : 提供了对唯一实例的受控访问。 由于在系统内存中只存在一个对象,因此可以节约系统资源。 缺点 : 单例类的扩展有很大的困难。 单例类的

    2024年02月04日
    浏览(52)
  • 【java】设计模式——单例模式

    单例模式要点 : 一个类只需要一个实例化对象; 必须自行创建实例; 必须自行向整个系统提供这个实例 实现 : 只提供 私有 构造方法; 有一个该类的 静态 私有对象; 提供一个静态 公有 方法用于创建、获取静态私有对象; 分析: 私有构造方法-不能随意创建实例; 静态

    2024年02月13日
    浏览(32)
  • JAVA设计模式——单例模式

    单例模式是应用最广的设计模式之一,也是程序员最熟悉的一个设计模式,使用单例模式的类必须保证只能有创建一个对象。 今天主要是回顾一下单例模式,主要是想搞懂以下几个问题 为什么要使用单例? 如何实现一个单例? 单例存在哪些问题? 单例对象的作用域的范围

    2024年02月16日
    浏览(34)
  • Java设计模式-单例模式

    单例模式是一种设计模式,它确保一个类只能创建一个实例,并提供一种全局访问这个实例的方式。在Java中,单例模式可以通过多种方式来实现,其中最常见的是使用私有构造函数和静态方法实现 在Java中,实现单例模式的方式有多种,其中最常见的实现方式包括以下几种:

    2024年02月01日
    浏览(36)
  • Java 设计模式——单例模式

    (1)单例模式 (Singleton Pattern) 是 Java 中最简单的设计模式之一。它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的

    2024年02月13日
    浏览(49)
  • Java设计模式---单例 工厂 代理模式

    单例模式是设计模式中的一种,属于创建型模式。在软件工程中,单例模式确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于那些需要频繁实例化然后引用,且创建新实例的开销较大的类,例如数据库连接池、缓存管理等。 意图 :保证一个类仅有一个实例

    2024年01月24日
    浏览(39)
  • 设计模式篇(Java):单例模式

    上一篇:设计模式篇(Java):前言(UML类图、七大原则) 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。 构造器私有化 (防止 new ) 类的内部创建对象 向外暴露一个静

    2024年02月11日
    浏览(50)
  • java设计模式-单例

    单例模式是一种创建型设计模式,它可以保证一个类只有一个实例,并提供全局访问点。单例模式在实际开发中经常使用,可以避免多个实例引起的资源浪费和同步问题。常见的java实现方式有多种。 饿汉式单例模式是指在类加载时就创建了单例对象,因此在调用时不需要再

    2024年01月18日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包