展开菜单
首页 精品内容 本月促销 装机必备 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电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

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

    IOS软件

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

    AI

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

    Mac软件 更多
    Aperty macOS版
    Aperty macOS版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw macOS版
    AionClaw macOS版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy macOS版
    Loomy macOS版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirBuddy 3 macOS版
    AirBuddy 3 macOS版

    AirBuddy 3 是面向 macOS 的蓝牙外设增强工具,可集中查看 AirPods、Beats 与苹果设备电量,提供快速连接、播放控制、电量提醒、桌面小组件和多台 Mac 外设接力等功能。

    AirServer macOS版
    AirServer macOS版

    AirServer macOS版可把 Mac 转换为无线投屏接收端,通过 AirPlay 或 Google Cast 接收手机、平板和电脑画面,并支持多设备同时连接、高清镜像及会话录制,适合家庭娱乐、课堂教学、会议演示和应用展示。

    AnyDesk macOS版
    AnyDesk macOS版

    AnyDesk macOS版是一款适用于远程办公、设备维护和客户支持的远程桌面工具,可连接并操作异地计算机,支持文件传输、无人值守访问、会话权限控制及跨平台协作。

    WINDOWS 更多
    讯飞绘文 网页版
    讯飞绘文 网页版

    讯飞绘文网页版是科大讯飞推出的AI图文创作与内容运营工具,覆盖热点选题、文章生成、稿件校对、智能配图、排版发布和数据复盘等环节,适合自媒体创作者及内容团队在线使用。

    Maya(Autodesk Maya) Windows版
    Maya(Autodesk Maya) Windows版

    Autodesk Maya 是面向影视、电视、动画和游戏行业的专业三维制作软件,集多边形与 NURBS 建模、角色绑定、关键帧和动作捕捉动画、Bifrost 特效模拟、材质开发及 Arnold 渲染于一体,

    Aperty Windows版
    Aperty Windows版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw Windows版
    AionClaw Windows版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy Windows版
    Loomy Windows版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirServer Windows版
    AirServer Windows版

    AirServer Windows版可把电脑变成AirPlay、Google Cast和Miracast无线投屏接收端,适合会议演示、课堂教学及家庭大屏分享。发送设备通常无需另装客户端,并可同时接收和智能排列多个镜像会话。