展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > Java设计模式的单例模式如何实现

Java设计模式的单例模式如何实现

单例模式单例模式顾名思义就是单一的实例,涉及到一个单一的类,该类负责创建自己的对象,同时确保只有一个对象被创建,并且提供一种可以访问这个对象的方式,可以直接访问,不需要实例化该类的对象。单例模式的特点:1.单例类只能有一个实例2.这个实例必须由单例类自己创建3.单例类需要提供给外界访问这个实例单例模式的作用:单例模式主要为了保证在Java应用程序中,一个类只有一个实例存在。1.单例模式的结构单例模式主要有以下角色:单例类只能创建一个实例的类访问类测试类,就是使用单例类的类2.单例模式的实现2.1饿汉式饿汉

单例模式

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

单例模式的特点:

1.单例类只能有一个实例

2.这个实例必须由单例类自己创建

3.单例类需要提供给外界访问这个实例

单例模式的作用:

单例模式主要为了保证在Java应用程序中,一个类只有一个实例存在。

1.单例模式的结构

单例模式主要有以下角色:

  • 单例类

只能创建一个实例的类

  • 访问类

测试类,就是使用单例类的类

2.单例模式的实现

2.1饿汉式

饿汉式:类加载时创建该单实例类对象

1.饿汉式-方式1 静态成员变量

创建 饿汉式静态成员变量 单例类

public class Demo1 {
 
    /**
     *私有构造方法  让外界不能创建该类对象
     */
    private Demo1(){}

    /**
     * 在类中创建该本类对象 static是由于外界获取该类对象的方法getInstance()是 static
     * 这个对象instance就是静态成员变量
     */
    private static Demo1 instance = new Demo1();

    /**
     * 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问
     */
    public static Demo1 getInstance(){
        return instance;
    }
}

创建 饿汉式静态成员变量 测试类(访问类)

public class Test1 {
    public static void main(String[] args) {
      //创建demo1类的对象 这个时候就无法通过new创建了,因为demo1的构造方法是私有的
        Demo1 instance = Demo1.getInstance();

        Demo1 instance1 = Demo1.getInstance();

        //判断两个对象是否是同一个
        System.out.println(instance == instance1);
        
    }
}

输出true 表明是同一个对象,指向同一块内存地址,这样我们就保证了Demo1单例类只有一个对象被创建

2.饿汉式-方式2 静态代码块

创建 饿汉式静态代码块 单例类

public class Demo2 {
    //饿汉式单例类  静态代码块

    /**
     *私有构造方法  让外界不能创建该类对象
     */
    private Demo2(){}

    /**
     *  声明一个静态的成员变量instance但是不赋值(不创建对象)
     *  没有为instance赋值,默认为null
     */
    private static  Demo2 instance;

    /**
     * 在静态代码快中为instance赋值(创建对象)
     */
    static {
        instance = new Demo2();
    }
    /**
     * 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问
     */
    public static Demo2  getInstance(){
        return instance;
    }
}

创建 饿汉式静态代码块 测试类

public class Test2 {
    public static void main(String[] args) {
        Demo2 instance = Demo2.getInstance();

        Demo2 instance1 = Demo2.getInstance();

        System.out.println(instance == instance1);
    }
}

输出true 表明是同一个对象,指向同一块内存地址,这样我们就保证了Demo2单例类只有一个对象被创建

3.饿汉式-方式3(枚举方式)

枚举类实现单例模式是十分推荐的一种单例实现模式,由于枚举类型是线程安全的,并且只会加载一次,这是十分符合单例模式的特点的,枚举的写法很简单,而且枚举方式是所有单例实现中唯一一个不会被破环的单例实现模式

单例类

//枚举方式创建单例
public enum Singleton {
     INSTANCE;
}

测试类

public class Test1 {
    public static void main(String[] args) {
    Singleton instance = Singleton.INSTANCE;
    Singleton instance1 = Singleton.INSTANCE;


        System.out.println(instance == instance1);
        //输出 true
        
    }
}

注意:

由于枚举方式是饿汉式,因此根据饿汉式的特点,枚举方式也会造成内存浪费,但是在不考虑内存问题下,枚举方式是首选,毕竟实现最简单了

2.2懒汉式

懒汉式:类加载时不会创建该单实例对象,首次使用该对象时才会创建

1.懒汉式-方式1 (线程不安全)

public class Demo3 {
    /**
     *私有构造方法  让外界不能创建该类对象
     */
    private Demo3(){}

    /**
     * 在类中创建该本类对象 static是由于外界获取该类对象的方法getInstance()是 static
     * 没有进行赋值(创建对象)
     */
    private static  Demo3 instance;


    /**
     * 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问
     */
    public static Demo3 getInstance(){
        //在首次使用该对象时创建,因此instance赋值也就是对象创建 就是在外界获取该单例类的方法getInstance()中创建
        instance = new Demo3();
        return instance;
    }

}
public class Test3 {
    public static void main(String[] args) {
        Demo3 instance = Demo3.getInstance();

        Demo3 instance1 = Demo3.getInstance();
        //判断两个对象是否是同一个
        System.out.println(instance == instance1);
    }
}

输出结果为false,表明我们创建懒汉式单例失败了。是因为我们在调用getInstance()时每次调用都会new一个实例对象,那么也就必然不可能相等了。

   // 如果instance为null,表明还没有创建该类的对象,那么就进行创建
        if(instance == null){
          instance = new Demo3();
        }
        //如果instance不为null,表明已经创建过该类的对象,根据单例类只能创建一个对象的特点,因此         //我们直接返回instance
        return instance;
    }

注意:

我们在测试是只是单线程,但是在实际应用中必须要考虑到多线程的问题。我们假设一种情况,线程1进入if判断然后还没来得及创建instance,这个时候线程1失去了cpu的执行权变为阻塞状态,线程2获取cpu执行权,然后进行if判断此时instance还是null,因此线程2为instance赋值创建了该单例对象,那么等到线程1再次获取cpu执行权,也进行了instance赋值创建了该单例对象,单例模式被破坏。

2.懒汉式-方式2 (线程安全)

我们可以通过加synchronized同步锁的方式保证单例模式在多线程下依旧有效

 public static synchronized Demo3 getInstance(){
        //在首次使用该对象时创建,因此instance赋值也就是对象创建 就是在外界获取该单例类的方法getInstance()中创建


        // 如果instance为null,表明还没有创建该类的对象,那么就进行创建

        if(instance == null){
          instance = new Demo3();
        }
        //如果instance不为null,表明已经创建过该类的对象,根据单例类只能创建一个对象的特点,因此我们直接返回instance
        return instance;
    }

注意:

虽然保证了线程安全问题,但是在getInstance()方法上添加了synchronized关键字,导致该方法执行效率很低(这是加锁的一个常见问题)。其实我们可以很容易发现,我们只是在判断instance时需要解决多线程的安全问题,而没必要在getInstance()上加锁

3.懒汉式-方式3(双重检查锁)

对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,没必要让每个线程必须持有锁才能调用该方法,我们可以调整加锁的时机。

public class Demo4 {
    /**
     *私有构造方法  让外界不能创建该类对象
     */
    private Demo4(){}

    /**
     *
     * 没有进行赋值(创建对象) 只是声明了一个该类的变量
     */
    private static Demo4 instance;


    /**
     * 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问
     */
    public static  Demo4 getInstance(){


        // (第一次判断)如果instance为null,表明还没有创建该类的对象,那么就进行创建
        if(instance == null){
            synchronized (Demo4.class){
                //第二次判断 如果instance不为null
                if(instance == null){
                    instance = new Demo4();
                }
            }

        }

        //如果instance不为null,表明已经创建过该单例类的对象,不需要抢占锁,直接返回
        return instance;
    }

}

双重检查锁模式完美的解决了单例、性能、线程安全问题,但是只是这样还是有问题的…

JVM在创建对象时会进行优化和指令重排,在多线程下可能会发生空指针异常的问题,可以使用volatile关键字,volatile可以保证可见性和有序性。

 private static volatile Demo4  instance;

Java设计模式的单例模式如何实现

如果发生指令重排 2 和 3 的步骤颠倒,那么instance会指向一块虚无的内存(也有可能是有数据的一块内存)

完整代码

public class Demo4 {
    /**
     *私有构造方法  让外界不能创建该类对象
     */
    private Demo4(){}

    /**
     * volatile可以保证有序性
     * 没有进行赋值(创建对象) 只是声明了一个该类的变量
     */
    private static volatile Demo4  instance;
    
    /**
     * 提供一个公共的访问方式,让外界可以获取该类的对象 static是因为外界不需要创建对象,直接通过类访问
     */
    public static  Demo4 getInstance(){
        // (第一次判断)如果instance为null,表明还没有创建该类的对象,那么就进行创建
        if(instance == null){
            synchronized (Demo4.class){
                //第二次判断 如果instance不为null
                if(instance == null){
                    instance = new Demo4();
                }
            }
        }

        //如果instance不为null,表明已经创建过该单例类的对象,不需要抢占锁,直接返回
        return instance;
    }
}

4.懒汉式-4 (静态内部类)

静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被final修饰,保证只被实例化一次,并且严格保证实例化顺序。

创建单例类

public class Singleton {

    private Singleton(){}

    /**
     *定义一个静态内部类
     */
    private static  class SingletonHolder{
        //在静态内部类中创建外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

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

创建测试类

public class Test4 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();

        Singleton instance1 = Singleton.getInstance();
        //判断两个对象是否是同一个
        System.out.println(instance == instance1);
    }
}

注意:

第一次加载Singleton类时不会去初始化INSTANCE,只有在调用getInstance()方法时,JVM加载SingletonHolder并初始化INSTANCE,这样可以保证线程安全,并且Singleton类的唯一性

静态内部类单例模式是一种开源项目比较常用的单例模式,在没有任何加锁的情况下保证多线程的安全,并且没有任何性能和空间上的浪费

3.单例模式的破坏

单例模式最重要的一个特点就是只能创建一个实例对象,那么如果能使单例类能创建多个就破坏了单例模式(除了枚举方式)破坏单例模式的方式有两种:

3.1序列化和反序列化

从以上创建单例模式的方式中任选一种(除枚举方式),例如静态内部类方式

//记得要实现Serializable序列化接口
public class Singleton implements Serializable {

    private Singleton(){}

    /**
     *定义一个静态内部类
     */
    private static  class SingletonHolder{
        //在静态内部类中创建外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

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

测试类

public class Test1 {

    public static void main(String[] args) throws IOException {
              writeObjectToFile();
    }


    /**
     * 向文件中写数据(对象)
     * @throws IOException
     */
    public static void writeObjectToFile() throws IOException {
        //1.获取singleton对象
        Singleton instance = Singleton.getInstance();
        //2.创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\1.txt"));
        //3.写对象
        oos.writeObject(instance);
        //4.释放资源
        oos.close();


    }
}

在d盘根目录下出现一个文件1.txt由于数据是序列化后的 咱也看不懂

然后我们从这个文件中读取instance对象

public static void main(String[] args) throws Exception {
             // writeObjectToFile();
        readObjectFromFile();
        readObjectFromFile();
    }
    /**
     * 从文件中读数据(对象)
     * @throws Exception
     */
    public static  void readObjectFromFile() throws Exception {

        //1.创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\1.txt"));
        //2.读对象
        Singleton instance = (Singleton) ois.readObject();
        System.out.println(instance);
        //3.释放资源
        ois.close();
    }

输出结果不相同,结论为:序列化破坏了单例模式,两次读的对象不一样了

com.xue.demo01.Singleton@2328c243
com.xue.demo01.Singleton@bebdb06

解决方案

在singleton中添加readResolve方法

  /**
     * 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
     * @return
     */
    public Object readResolve(){
        return SingletonHolder.INSTANCE;
    }

重新进行写和读,发现两次读的结果是相同的,解决了序列化破坏单例模式的问题

为什么在singleton单例类中添加readResolve方法就可以解决序列化破坏单例的问题呢,我们在ObjectInputStream源码中在readOrdinaryObject方法中

 private Object readOrdinaryObject(boolean unshared)
        throws IOException{
//代码段   
Object obj;
        try {
            //isInstantiable如果一个实现序列化的类在运行时被实例化就返回true
            //desc.newInstance()会通过反射调用无参构造创建一个新的对象
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

   //代码段

   if (obj != null &&
            handles.lookupException(passHandle) == null &&
       //hasReadResolveMethod 如果实现序列化接口的类中定义了readResolve方法就返回true
            desc.hasReadResolveMethod())
        {
       //通过反射的方式调用被反序列化类的readResolve方法
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
       
    //代码段
 }
3.2反射

从以上创建单例模式的方式中任选一种(除枚举方式),例如静态内部类方式

测试类

public class Test1 {

    public static void main(String[] args) throws Exception {


        //1.获取Singleton的字节码对象
        Class singletonClass = Singleton.class;

        //2.获取无参构造方法对象
        Constructor cons = singletonClass.getDeclaredConstructor();

        //3.取消访问检查
        cons.setAccessible(true);
        //4.反射创建对象
        Singleton instance1 = (Singleton) cons.newInstance();

        Singleton instance2 = (Singleton) cons.newInstance();

        System.out.println(instance1 == instance2);
        //输出false 说明反射破坏了单例模式
    }


}

解决方案:

public class Singleton  {

    //static是为了都能访问
    private static boolean flag = false;

    private Singleton() {
        //加上同步锁,防止多线程并发问题
        synchronized (Singleton.class) {
            //判断flag是否为true,如果为true说明不是第一次创建,抛异常
            if (flag) {
                throw new RuntimeException("不能创建多个对象");
            }
            //flag的值置为true
            flag = true;
        }
    }

    /**
     *定义一个静态内部类
     */
    private static  class SingletonHolder{
        //在静态内部类中创建外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}
本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
作者最新文章
编程开发 Java
相关文章 更多
精品专题 更多
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

Mac软件 更多
photoshop
photoshop

Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

Blender
Blender

Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

灵活计算器
灵活计算器

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

图几
图几

图几是一款适用于 macOS 的截图、标注与美化工具,支持离线操作保障隐私。界面整理和高频系统操作被放到一起考虑,桌面或窗口内容一多时,管理起来会更省心。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Mac
WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

WINDOWS 更多
3dmax(3ds max)
3dmax(3ds max)

Autodesk 3ds Max 是一款专业的三维建模、动画与渲染软件,广泛应用于建筑可视化、游戏开发、影视动画、广告设计和产品展示等领域。

photoshop
photoshop

Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

Blender
Blender

Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

Windows 10
Windows 10

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

傲梅轻松备份
傲梅轻松备份

傲梅轻松备份是一款专业易用的数据备份软件,为重要数据提供安全保障。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Mac
Wise Folder Hider Pro
Wise Folder Hider Pro

Wise Folder Hider Pro 是一款专业级文件和文件夹隐藏加密软件,为私密数据添加多重保护。高频操作更强调就近处理,浏览、整理和跨目录移动文件时,来回切换和重复点击都会少很多。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。