0

0

一起来分析java设计模式之单例

WBOY

WBOY

发布时间:2022-11-07 16:56:26

|

1230人浏览过

|

来源于掘金

转载

本篇文章给大家带来了关于java的相关知识, 其中主要介绍了关于设计模式中单例模式的相关内容,单例就一条基本原则是单例对象的类只会被初始化一次,下面一起来看一下,希望对大家有帮助。

一起来分析java设计模式之单例

推荐学习:《java视频教程

单元素的枚举类型经常成为实现 Singleton 的最佳方法 。

什么是单例?就一条基本原则,单例对象的类只会被初始化一次。在 Java 中,我们可以说在 JVM 中只存在该类的唯一一个对象实例。在 Android 中,我们可以说在程序运行期间,该类有且仅有一个对象实例。

单例模式的简单实现步骤:

立即学习Java免费学习笔记(深入)”;

  • 构造方法私有,保证无法从外部通过 new 的方式创建对象。

  • 对外提供获取该类实例的静态方法。

  • 类的内部创建该类的对象,通过第 2 步的静态方法返回。

按照上述步骤写下你认为比较严谨的单例模式,然后看看你所写下的单例能否满足以下条件:

  • 你的单例按需加载吗?
  • 你的单例线程安全吗?涉及到并发三要素:原子性、可见性、有序性
  • 你的单例暴力反射和序列化安全吗?

一、饿汉式

//JAVA实现public class SingleTon {    //第三步创建唯一实例
    private static SingleTon instance = new SingleTon();    
    //第一步构造方法私有
    private SingleTon() {
    }    
    //第二步暴露静态方法返回唯一实例
    public static SingleTon getInstance() {        return instance;
    } 
}//Kotlin实现object SingleTon

优点:设计简单 ,解决了多线程实例化的问题。

缺点:在虚拟机加载SingleTon类的时候,将会在初始化阶段为类静态变量赋值,也就是在虚拟机加载该类的时候(此时可能并没有调用 getInstance 方法)就已经调用了 new SingleTon(); 创建了该对象的实例,之后不管这个实例对象用不用,都会占据内存空间。

二、懒汉式

//JAVA实现public class SingleTon {    //创建唯一实例
    private static SingleTon instance = null;    
    private SingleTon() {
    }    
    public static SingleTon getInstance() {        //延迟初始化 在第一次调用 getInstance 的时候创建对象
        if (instance == null) {
            instance = new SingleTon();
        }        return instance;
    } 
}//Kotlin实现class SingleTon private constructor() {    companion object {        private var instance: SingleTon? = null
            get() {                if (field == null) {
                    field = SingleTon()
                }                return field
            }        fun get(): SingleTon{            return instance!!
        }
    }
}

优点:设计也是比较简单的,和饿汉式不同,当这个Singleton被加载的时候,被static修饰的静态变量将会被初始化为null,这个时候并不会占用内存,而是当第一次调用getInstance方法的时候才会被初始化实例对象,按需创建。

缺点:在单线程环境下是没有问题的,在多线程环境下,会产生线程安全问题。在有两个线程同时 运行到了 instane == null这个语句,并且都通过了,那他们就会都各自实例化一个对象,这样就又不是单例了。

如何解决懒汉式在多线程环境下的多实例问题?

  • 静态内部类

    //JAVA实现public class SingleTon {    
        private static class InnerSingleton{        private static SingleTon singleTon  = new SingleTon();
        }    public SingleTon getInstance(){        return InnerSingleton.singleTon;
        }    
        private SingleTon() {
        }
    }//kotlin实现class SingleTon private constructor() {
        companion object {        val instance = InnerSingleton.instance
        }    private object InnerSingleton {        val instance = SingleTon()
        }
    }
  • 直接同步方法

    //JAVA实现public class SingleTon {    //创建唯一实例
        private static SingleTon instance = null;    
        private SingleTon() {
        }    
        public static synchronized SingleTon getInstance() {        if (instance == null) {
                instance = new SingleTon();
            }        return instance;
        } 
    }//Kotlin实现class SingleTon private constructor() {  companion object {      private var instance: SingleTon? = null
              get() {              if (field == null) {
                      field = SingleTon()
                  }              return field
              }      @Synchronized
          fun get(): SingleTon{          return instance!!
          }
      }
    }

    优点:加锁只有一个线程能实例该对象,解决了线程安全问题。

    缺点:对于静态方法而言,synchronized关键字会锁住整个 Class,每次调用getInstance方法都会线程同步,效率十分低下,而且当创建好实例对象之后,也就不必继续进行同步了。

    备注:此处的synchronized保证了操作的原子性和内存可见性。

  • 同步代码块(双重检锁方式DCL)

    方舟订单管理系统
    方舟订单管理系统

    系统开发由二当家的编写,代码完全开源,可自行修改源码,欢迎使用! 1、网站采用php语言开发,更安全、稳定、无漏洞、防注入、防丢单。 2、记录订单来路,客户IP记录及分析,订单数据统计 3、订单邮件提醒、手机短信提醒,让您第一时间追踪订单,大大提升了发货效率,提高订单成交率。 4、多种支付方式,包含:货到付款、支付宝接口、网银支付,可设置在线支付的折扣比率。 5、模板样式多样化,一个订单放到多个网

    下载
    //JAVA实现 public class SingleTon {    //创建唯一实例
        private static volatile SingleTon instance = null;    
        private SingleTon() {
        }    
        public static SingleTon getInstance() {        if (instance == null) {
                synchronized (SingleTon.class) {   
                    if (instance == null) {
                        instance = new SingleTon();
                    }
                }
            }        return instance;
        } 
    }//kotlin实现class SingleTon private constructor() {    companion object {        val instance: SingleTon by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
                SingleTon() 
            }
      }
    }
    或者class SingleTon private constructor() {    companion object {        @Volatile private var instance: SingleTon? = null
            fun getInstance() =
                  instance ?: synchronized(this) {
                      instance ?: SingleTon().also { instance = it }
                  }
      }
    }

    优点:添加了一个同步代码块,在同步代码块中去判断实例对象是否存在,如果不存在则去创建,这个时候其实就完全可以解决问题了,因为虽然是多个线程去获取实例对象,但是在同一个时间也只会有一个线程会进入到同步代码块,那么这个时候创建好对象之后,其他线程即便再次进入同步代码块,由于已经创建好了实例对象,便直接返回即可。但是为什么还要在同步代码块的上一步再次去判断instance为空呢?这个是由于当我们创建好实例对象之后,直接去判断此实例对象是否为空,如果不为空,则直接返回就好了,就避免再次进去同步代码块了,提高了性能。

    缺点:无法避免暴力反射创建对象。

    备注:此处的volatile发挥了内存可见性及防止指令重排序作用。

三、枚举实现单例

public enum SingletonEnum {    INSTANCE;    public static void main(String[] args) {        System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);
    }
}

枚举实现单例是最为推荐的一种方法,因为就算通过序列化,反射等也没办法破坏单例性。(关于Android使用枚举会产生性能问题的说法,这应该是Android 2.x系统之前内存紧张的时代了,现在已经Android 13了,相信某些场合枚举所带来的便利远远大于这点所谓的性能影响)

四、如何避免单例模式反射攻击

以最初的DCL为测试案例,看看如何进行反射攻击及又如何在一定程度上避免反射攻击。反射攻击代码如下:

 public static void main(String[] args) {

     SingleTon singleton1 = SingleTon.getInstance();
     SingleTon singleton2 = null;

     try {
         Class clazz = SingleTon.class;
         Constructor constructor = clazz.getDeclaredConstructor();
         constructor.setAccessible(true);
         singleton2 = constructor.newInstance();
     } catch (Exception e) {
         e.printStackTrace();
     }

     System.out.println("singleton1.hashCode():" + singleton1.hashCode());
     System.out.println("singleton2.hashCode():" + singleton2.hashCode());
 }

执行结果:

 singleton1.hashCode():1296064247
 singleton2.hashCode():1637070917

通过执行结果发现通过反射破坏了单例。 如何保证反射安全呢?只能以暴制暴,当已经存在实例的时候再去调用构造函数直接抛出异常,对构造函数做如下修改:

  public class SingleTon {     //创建唯一实例
     private static volatile SingleTon instance = null;   
     private SingleTon() {         if (instance != null) {             throw new RuntimeException("单例构造器禁止反射调用");
         }
     }   
     public static SingleTon getInstance() {         if (instance == null) {
           synchronized (SingleTon.class) {   
               if (instance == null) {
                   instance = new SingleTon();
               }
           }
       }       return instance;
     } 
 }

此时可防御反射攻击,抛出异常如下:

 java.lang.reflect.InvocationTargetException
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
 at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:45)
 at com.imock.demo.TestUtil.main(TestUtil.java:33)
 Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
 at com.imock.demo.SingleTon.(SingleTon.java:16)
 ... 6 more Exception in thread "main" java.lang.NullPointerException
 at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:49)
 at com.imock.demo.TestUtil.main(TestUtil.java:33) 
 Process finished with exit code 1

然后我们把上述测试代码修改如下(调换了singleton1的初始化顺序)

 public static void main(String[] args) {
     SingleTon singleton2 = null;

     try {
         Class clazz = SingleTon.class;
         Constructor constructor = clazz.getDeclaredConstructor();
         constructor.setAccessible(true);
         singleton2 = constructor.newInstance();
     } catch (Exception e) {
         e.printStackTrace();
     }

     System.out.println("singleton2.hashCode():" + singleton2.hashCode());

     SingleTon singleton1 = SingleTon.getInstance(); //调换了位置,在反射之后执行
     System.out.println("singleton1.hashCode():" + singleton1.hashCode());
 }

执行结果:

 singleton2.hashCode():1296064247
 singleton1.hashCode():1637070917

发现此防御未起到作用。

缺点:

  • 如果反射攻击发生在正常调用getInstance之前,每次反射攻击都可以获取单例类的一个实例,因为即使私有构造器中使用了静态成员(instance) ,但单例对象并没有在类的初始化阶段被实例化,所以防御代码不生效,从而可以通过构造器的反射调用创建单例类的多个实例;
  • 如果反射攻击发生在正常调用之后,防御代码是可以生效的;

如何避免序列化攻击?只需要修改反序列化的逻辑就可以了,即重写 readResolve() 方法,使其返回统一实例。

   protected Object readResolve() {       return getInstance();
   }

脆弱不堪的单例模式经过重重考验,进化成了完全体,延迟加载,线程安全,反射及序列化安全。简易代码如下:

  • 饿汉模式

    public class SingleTon {    private static SingleTon instance = new SingleTon();    
        private SingleTon() {        if (instance != null) {              throw new RuntimeException("单例构造器禁止反射调用");
             }
        }    public static SingleTon getInstance() {        return instance;
        } 
    }
  • 静态内部类

    public class SingleTon {    
        private static class InnerStaticClass{        private static SingleTon singleTon  = new SingleTon();
        }    public SingleTon getInstance(){        return InnerStaticClass.singleTon;
        }    
        private SingleTon() {       if (InnerStaticClass.singleTon != null) {           throw new RuntimeException("单例构造器禁止反射调用");
           }
        }
    }
  • 懒汉模式

    public class SingleTon {    //创建唯一实例
        private static SingleTon instance = null;    
        private SingleTon() {        if (instance != null) {              throw new RuntimeException("单例构造器禁止反射调用");
            }
        }    
        public static SingleTon getInstance() {        //延迟初始化 在第一次调用 getInstance 的时候创建对象
            if (instance == null) {
                instance = new SingleTon();
            }        return instance;
        } 
    }

    缺点:

    • 如果反射攻击发生在正常调用getInstance之前,每次反射攻击都可以获取单例类的一个实例,因为即使私有构造器中使用了静态成员(instance) ,但单例对象并没有在类的初始化阶段被实例化,所以防御代码不生效,从而可以通过构造器的反射调用创建单例类的多个实例;
    • 如果反射攻击发生在正常调用之后,防御代码是可以生效的。

(枚举实现单例是最为推荐的一种方法,因为就算通过序列化,反射等也没办法破坏单例性,底层实现比如newInstance方法内部判断枚举抛异常)

推荐学习:《java视频教程

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

178

2026.01.28

包子漫画在线官方入口大全
包子漫画在线官方入口大全

本合集汇总了包子漫画2026最新官方在线观看入口,涵盖备用域名、正版无广告链接及多端适配地址,助你畅享12700+高清漫画资源。阅读专题下面的文章了解更多详细内容。

35

2026.01.28

ao3中文版官网地址大全
ao3中文版官网地址大全

AO3最新中文版官网入口合集,汇总2026年主站及国内优化镜像链接,支持简体中文界面、无广告阅读与多设备同步。阅读专题下面的文章了解更多详细内容。

79

2026.01.28

php怎么写接口教程
php怎么写接口教程

本合集涵盖PHP接口开发基础、RESTful API设计、数据交互与安全处理等实用教程,助你快速掌握PHP接口编写技巧。阅读专题下面的文章了解更多详细内容。

2

2026.01.28

php中文乱码如何解决
php中文乱码如何解决

本文整理了php中文乱码如何解决及解决方法,阅读节专题下面的文章了解更多详细内容。

4

2026.01.28

Java 消息队列与异步架构实战
Java 消息队列与异步架构实战

本专题系统讲解 Java 在消息队列与异步系统架构中的核心应用,涵盖消息队列基本原理、Kafka 与 RabbitMQ 的使用场景对比、生产者与消费者模型、消息可靠性与顺序性保障、重复消费与幂等处理,以及在高并发系统中的异步解耦设计。通过实战案例,帮助学习者掌握 使用 Java 构建高吞吐、高可靠异步消息系统的完整思路。

8

2026.01.28

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

24

2026.01.27

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

122

2026.01.26

edge浏览器怎样设置主页 edge浏览器自定义设置教程
edge浏览器怎样设置主页 edge浏览器自定义设置教程

在Edge浏览器中设置主页,请依次点击右上角“...”图标 > 设置 > 开始、主页和新建标签页。在“Microsoft Edge 启动时”选择“打开以下页面”,点击“添加新页面”并输入网址。若要使用主页按钮,需在“外观”设置中开启“显示主页按钮”并设定网址。

72

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
CSS3 教程
CSS3 教程

共18课时 | 4.9万人学习

Git 教程
Git 教程

共21课时 | 3.1万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号