小工具      在线工具  汉语词典  css  js  c++  java

单例模式

单例模式,java 额外说明

收录于:40天前

单例模式概述

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有一个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全球接入点来访问该实例。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。

介绍

目的: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决方案: 一个全局使用的类频繁地创建与销毁。
何时使用: 当您想控制实例数目,节省系统资源的时候。
怎么解决: 判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码: 构造函数是私有的。
优势:

  1. 在内存里只有一个实例,减少内存开销,尤其是频繁的创建和销毁实例。
  2. 避免资源的多重占用,可以提高系统的性能。
  3. 允许可变数量的实例。我们可以在单例模式的基础上进行扩展,使用类似于单例控制的方法来获取指定数量的对象实例。

缺点:

  1. 由于单例模式中没有抽象层,因此扩展单例类的困难
  2. 单例类的职责过重,在一定程度上违反“单一责任原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
  3. 滥用单身会带来一些负面问题。例如,为了节省资源,将数据库连接池对象设计为单例类,这可能会导致过多的程序共享连接池对象,导致连接池溢出;现在很多面向对象语言(如Java、C#)运行环境都提供了自动垃圾收集技术。因此,如果实例化的对象长时间不使用,系统会认为它是垃圾,自动销毁并回收资源,并在下次使用时重新实例化。化,这将导致对象状态的丢失。

使用场景:

  1. 系统只需要一个实例对象。例如,系统需要一个唯一的序列号生成器,或者由于资源消耗过多而只允许创建一个对象。
  2. 客户端调用类的单个实例只允许使用一个公共访问点,并且不能通过除该公共访问点之外的其他方式访问该实例。
  3. 当系统只需要一个类的一个实例时,应该使用单例模式。相反,如果一个类可以有多个实例共存,则需要将单例模式改进为多实例模式。

应用:

  1. Windows 是多进程、多线程的。在操作文件时,难免会有多个进程或者线程同时操作一个文件。因此,所有文件处理都必须通过唯一的实例来执行。
  2. 一些设备管理器通常设计为单例模式。例如,一台计算机有两台打印机,输出时必须处理两台打印机无法打印同一个文件。

防范措施:

  1. 一个单例类只能有一个实例。
  2. 单例类必须创建自己的唯一实例。
  3. 单例类必须向所有其他对象提供此实例。
  4. getInstance()方法中需要使用同步锁synchronized(Singleton.class),防止多个线程同时进入,导致实例被多次实例化。

结构

一种角色

单例类(Singleton)
包含一个实例且能自行创建这个实例的类。

结构图


案例实现

案例描述

单例模式的几种实现方式:懒惰风格、饥饿风格、双重检查锁/双重检查锁、注册/静态内部类、枚举

案例类图

代码实现

由于实现方法有多种,这里为了方便测试,继承了同一个接口。之所以继承Serialized,是因为下面的方法是关于破解反序列化机制的。

public interface Singleton extends Serializable {
    
    void show();
}

懒汉式

是否延迟初始化:是的
多线程安全吗?
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式延迟加载(lazy loading)很明显,只有在调用getInstance是才会创建实例,不要求线程安全,在多线程不能正常工作。
实施难度:

public class LazySingleton implements Singleton {
    

    private static LazySingleton INSTANCE;

    private LazySingleton() {
    
    }

    public static LazySingleton getInstanceN() {
    
        if (INSTANCE == null) {
    
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }

    @Override
    public void show() {
    
        System.out.println("Hello lazy singleton pattern");
    }
}

是否延迟初始化:是的
多线程安全吗?是的
这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
实施难度:

public class LazySingleton implements Singleton {
    

    private static LazySingleton INSTANCE;

    private LazySingleton() {
    
    }

    public static synchronized LazySingleton getInstanceY() {
    
        if (INSTANCE == null) {
    
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }

    @Override
    public void show() {
    
        System.out.println("Hello lazy singleton pattern");
    }
}

饿汉式

是否延迟初始化:
多线程安全吗?是的
这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
实施难度:

public class HungrySingleton implements Singleton {
    

    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton(){
    }

    public static HungrySingleton getInstance(){
    
        return INSTANCE;
    }

    @Override
    public void show(){
    
        System.out.println("Hello singleton pattern");
    }
}

DCL双检锁

双重检查锁/双重检查锁(DCL,双重检查锁定)
JDK版本: JDK1.5起
是否延迟初始化:
多线程安全吗?
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。
实施难度: 较复杂

public class HungrySingleton implements Singleton {
    

    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton(){
    }

    public static HungrySingleton getInstance(){
    
        return INSTANCE;
    }

    @Override
    public void show(){
    
        System.out.println("Hello singleton pattern");
    }
}

登记式/静态内部类

是否延迟初始化:
多线程安全吗?
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟饿汉式不同的是:饿汉式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉式就显得很合理。
实施难度: 一般

public class HungrySingleton implements Singleton {
    

    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton(){
    }

    public static HungrySingleton getInstance(){
    
        return INSTANCE;
    }

    @Override
    public void show(){
    
        System.out.println("Hello singleton pattern");
    }
}

枚举

JDK版本: JDK1.5起
是否延迟初始化:
多线程安全吗?
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。
实施难度:

public enum EnumSingleton implements Singleton{
    

    INSTANCE;

    @Override
    public void show() {
    
        System.out.println("Hello enum singleton pattern");

    }
}

注意

一般来说,不建议使用第1、2种懒人方法,推荐使用第3种饿人方法。只有显式实现延迟加载效果时才会使用第五种注册方法。如果涉及到反序列化和创建对象,可以尝试使用第六种枚举方法。如果有其他特殊需求,可以考虑使用第四种双重检查锁定方法。

demo调用

public class SingletonPattern {
    

    public static void main(String[] args) {
    
        System.out.println("懒汉式 线程不安全");
        isSame(LazySingleton.getInstanceN(), LazySingleton.getInstanceN());
        System.out.println("懒汉式 线程安全");
        isSame(LazySingleton.getInstanceY(), LazySingleton.getInstanceY());
        System.out.println("饿汉式");
        isSame(HungrySingleton.getInstance(),HungrySingleton.getInstance());
        System.out.println("双检锁/双重校验锁");
        isSame(DclSingleton.getInstance(),DclSingleton.getInstance());
        System.out.println("登记式/静态内部类");
        isSame(InnerSingleton.getInstance(),InnerSingleton.getInstance());
        System.out.println("枚举");
        isSame(EnumSingleton.INSTANCE,EnumSingleton.INSTANCE);
    }

    private static void isSame(Singleton s1, Singleton s2){
    
        System.out.println(">>>>>>>>>>>>>>>>>>>>s1<<<<<<<<<<<<<<<<<<<<");
        s1.show();
        System.out.println(">>>>>>>>>>>>>>>>>>>>s2<<<<<<<<<<<<<<<<<<<<");
        s2.show();
        System.out.println(">>>>>>>>>>>>>>>>>>isSame<<<<<<<<<<<<<<<<<<");
        System.out.println(s1+"\n"+s2);
        System.out.println(s1==s2);
    }
}

关于优化

使用反射打破单例模式
反射机制可以破解除枚举外的所有单例模式

public class SingletonPattern {
    

    public static void main(String[] args) {
    
        try {
    
            refCrack(LazySingleton.class);
            refCrack(HungrySingleton.class);
            refCrack(DclSingleton.class);
            refCrack(InnerSingleton.class);
        } catch (Exception e) {
    
            e.printStackTrace();
        }
    }

    private static void isSame(Singleton s1, Singleton s2) {
    
        System.out.println(">>>>>>>>>>>>>>>>>>>>s1<<<<<<<<<<<<<<<<<<<<");
        s1.show();
        System.out.println(">>>>>>>>>>>>>>>>>>>>s2<<<<<<<<<<<<<<<<<<<<");
        s2.show();
        System.out.println(">>>>>>>>>>>>>>>>>>isSame<<<<<<<<<<<<<<<<<<");
        System.out.println(s1 + "\n" + s2);
        System.out.println(s1 == s2);
    }

    /** * 反射破解 */
    private static <T extends Singleton> void refCrack(Class<T> tClass) throws Exception {
    
        Constructor<T> constructor = tClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton s1 = constructor.newInstance();
        Singleton s2 = constructor.newInstance();
        isSame(s1, s2);
    }
}

这里的s1和s1是两个不同的实例,输出结果如下
image.png
解决方案:
在构造器中加入判断,多次抛出异常

public class LazySingleton implements Singleton {
    

    private static LazySingleton INSTANCE;

    private LazySingleton() {
    
        //防止反射破解,除枚举外的任何方式
        if (INSTANCE != null) {
    
            throw new RuntimeException();
        }
    }

    public static LazySingleton getInstanceN() {
    
        if (INSTANCE == null) {
    
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }

    public static synchronized LazySingleton getInstanceY() {
    
        if (INSTANCE == null) {
    
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }

    @Override
    public void show() {
    
        System.out.println("Hello lazy singleton pattern");
    }
}

使用反序列化机制打破单例模式
反序列化机制破解单例模式(枚举除外)

public class SingletonPattern {
    

    public static void main(String[] args) {
    
        try {
    
            //反序列化破解
            System.out.println("*********************反序列化机制破解***********************");
            desCrack(LazySingleton.getInstanceN());
            desCrack(HungrySingleton.getInstance());
            desCrack(DclSingleton.getInstance());
            desCrack(InnerSingleton.getInstance());
            //反射破解
            System.out.println("************************反射破解**************************");
            refCrack(LazySingleton.class);
            refCrack(HungrySingleton.class);
            refCrack(DclSingleton.class);
            refCrack(InnerSingleton.class);
        } catch (Exception e) {
    
            e.printStackTrace();
        }
    }

    private static void isSame(Singleton s1, Singleton s2) {
    
        System.out.println(">>>>>>>>>>>>>>>>>>>>s1<<<<<<<<<<<<<<<<<<<<");
        s1.show();
        System.out.println(">>>>>>>>>>>>>>>>>>>>s2<<<<<<<<<<<<<<<<<<<<");
        s2.show();
        System.out.println(">>>>>>>>>>>>>>>>>>isSame<<<<<<<<<<<<<<<<<<");
        System.out.println(s1 + "\n" + s2);
        System.out.println(s1 == s2);
    }

    /** * 反射破解 */
    private static <T extends Singleton> void refCrack(Class<T> tClass) throws Exception {
    
        Constructor<T> constructor = tClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton s1 = constructor.newInstance();
        Singleton s2 = constructor.newInstance();
        isSame(s1, s2);
    }

    /** * 反序列化机制破解 */
    private static void desCrack(Singleton s1) throws Exception {
    
        //将s1写入本地某个路径
        FileOutputStream fos = new FileOutputStream("desCrack");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);
        oos.close();
        fos.close();

        //从本地某个路径读取写入的对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("desCrack"));
        Singleton s2 = (Singleton) ois.readObject();
        isSame(s1, s2);
    }
}

解决方案:
在单例类中定义方法readResolve

public class LazySingleton implements Singleton {
    

    private static LazySingleton INSTANCE;

    private LazySingleton() {
    
        //防止反射破解,除枚举外的任何方式
        if (INSTANCE != null) {
    
            throw new RuntimeException();
        }
    }

    public static LazySingleton getInstanceN() {
    
        if (INSTANCE == null) {
    
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }

    public static synchronized LazySingleton getInstanceY() {
    
        if (INSTANCE == null) {
    
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }

    private Object readResolve() throws ObjectStreamException {
    
        return INSTANCE;
    }
    @Override
    public void show() {
    
        System.out.println("Hello lazy singleton pattern");
    }
}
. . .

相关推荐

额外说明

Elasticsearch索引排序原理详解

Elasticsearch Index Sorting 原理 Elasticsearch 是一款搜索引擎,它使用倒排索引来通过分词去检索数据,倒排索引里面的数据(docID)是有顺序的,默认是写入顺序,在大部分情况下,当检索数据时,都需要遍历倒排索引里的

额外说明

MFC对话框打开并保存文件显示路径

1. 对话框打开文件 直接上代码: void CHexMergeToolDlg::OnBnClickedButtonApp() { // TODO: 在此添加控件通知处理程序代码 CString sEx; //扩展名 CString sPa

额外说明

day15---(05)在线教育项目整合阿里云播放器

1、在小节vo类添加视频id属性(注意:字段名必须和EduVideo字段一样) @Data public class VideoVo { private String id; private String title; //视频

额外说明

【JAVA-Day14】深入了解 Java 中的 while 循环语句

深入了解 Java 中的 while 循环语句 深入了解 Java 中的 while 循环语句 摘要 引言 一、什么是 while 循环语句 二、while 循环语句的语法和使用场景 使用场景 三、while 循环的优势和使用场景 优势 使用建议 四、总

额外说明

企业级实战——品优购电商系统开发-21.22.逆向工程代码生成

QQ 1274510382 Wechat JNZ_aming 商业联盟 QQ群538250800 技术搞事 QQ群599020441 解决方案 QQ群152889761 加入我们 QQ群649347320 共享学习 QQ群674240731 纪年科技am

额外说明

OpenCV基础知识(10)— 人脸识别(人脸跟踪、眼睛跟踪、行人跟踪、车牌跟踪和人脸识别)

前言:Hello大家好,我是小哥谈。人脸识别是基于人的脸部特征信息进行身份识别的一种生物识别技术,也是计算机视觉重点发展的技术。机械学习算法诞生之后,计算机可以通过摄像头等输入设备自动分析图像中包含的内容信息,随着技术的不断发展,现在已经有了多种人脸识别

额外说明

Eclipse中出现javax.servlet.ServletException: javax.servlet.jsp.JspTagException: ...500问题

今天尝试用jsp连接mysql数据库出现了报错. 具体报错: 网址报错: Type :Exception Report(异常报告) Message: javax.servlet.ServletException: javax.servlet.jsp.Js

额外说明

Linux命令200例:lsattr用于查看文件或目录的属性

-作者简介,黑夜开发者,全栈领域新星创作者✌,2023年6月csdn上海赛道top4。 -本文已收录于专栏:Linux命令大全。 -本专栏我们会通过具体的系统的命令讲解加上鲜活的实操案例对各个命令进行深入讲解。欢迎提前锁定关注。 文章目录 一、简介 二、

额外说明

Java基础之lambda表达式(JDK1.8新特性)

文章目录 Lambda表达式 各种函数式接口 Lambda的语法 Lambda 表达实例 举例说明 变量作用域 处理lambda 表达式 变量作用域 函数式接口 使用实例1 使用实例2 使用示例3(集合排序) 使用示例4(按照对象属性给list排序) 使

额外说明

总结一下这几年在外包软件开发公司体验,项目开发交付的痛苦,如何避开并且能够增加收益。

毕业比现在一直在外包公司做外包项目,体验过各种甲方和老板变态需求。今天总结几条在外包公司开发项目时经常遇到的不舒服要求。 需求经常变动,客户没有看到软件前想法和开发出测试版想法不一样,经常学到开发出来客户想法要不一样,所以就得改来改去。 客户急要,外包项

ads via 小工具