Java之单例模式

Java之单例模式

单例模式的优点:

  1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要
    比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动
    时直接产生一个单例对象,然后永久驻留内存的方式来解决
  2. 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计
    一个单例类,负责所有数据表的映射处理
    • 常见的五种单例模式实现方式:
    – 主要:
    • 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
    • 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
    – 其他:
    • 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
    • 静态内部类式(线程安全,调用效率高。 但是,可以延时加载) • 枚举单例(线程安全,调用效率高,不能延时加载)

饿汉式实现(单例对象立即加载)

要点: 饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!

package com.ahzy;
/** * 单例饿汉式 * @author 晓宇码匠 * 频繁的调用这个实例的时候用饿汉式 */
public class SingletonDome01 {
   
    //类初始化时加载这个对象,没有延迟加载的优势,加载类时,是天然的线程安全
    private static SingletonDome01 instance = new SingletonDome01();
    //私有化构造器
    private SingletonDome01(){
      
    }
    //方法没有同步,调用效率高
    public static SingletonDome01 getInstance() {
   
        return instance;
    }
}

懒汉式实现(单例对象延迟加载)

要点: lazy load! 延迟加载, 懒加载! 真正用的时候才加载!
问题:资源利用率高了。但是,每次调用getInstance()方法都要同步,并发效率较低。

package com.ahzy;
/** * 单例懒汉式 * @author 晓宇码匠 * 资源利用率高。但是每次调用getIntance()都要同步,并发效率低 * 当创建实例的代价较大时,用懒汉式 */
public class SingletonDome02 {
   
    private static SingletonDome02 instance;
    private SingletonDome02() {
   
    }
    //方法同步,调用效率低
    public static synchronized SingletonDome02 getInstance(){
   
        //延迟加载,懒加载,真正用的时候再加载
        if(instance==null){
   
            instance = new SingletonDome02();
        }
        return instance;
    }
}

双重检测锁实现

要点:这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。
问题: 由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。

package com.ahzy;
/** * 单例双重检测锁模式 * @author 晓宇码匠 * 问题:由于编译器优化原因和JVM底层模式内部原因,偶尔会出现数据调整问题,不建议使用 */
public class SingletonDome03 {
   
    private static SingletonDome03 instance = null;
    private SingletonDome03() {
   }
    /* * 这个模式将同步内容下方到if内部,提高了执行的效率 * 不必每次获得对象时都要进行同步,只有第一次才同步 * 创建了以后就没必要了。 */
    public static SingletonDome03 getInstance() {
   
        if (instance == null) {
   
            SingletonDome03 sc;
            synchronized (SingletonDome03.class) {
   
                sc = instance;
                if (sc == null) {
   
                    synchronized (SingletonDome03.class) {
   
                        if (sc == null) {
   
                            sc = new SingletonDome03();
                        }
                    }
                    instance=sc;
                }
            }
        }
        return instance;
    }
}

静态内部类实现方式(也是一种懒加载方式)

要点:

  • 外部类没有static属性,则不会像饿汉式那样立即加载对象。
  • 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。 instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
  • 兼备了并发高效调用和延迟加载的优势!
package com.ahzy;
/** * 静态类部类(也是一种懒加载) * @author 晓宇码匠 * 特点:延迟加载,调用效率高,线程安全 */
public class SingletonDome04 {
   
    private static class SingletClassInstance{
   
        private static SingletonDome04 instance = new SingletonDome04();
    }
    private SingletonDome04(){
     
    }
    public static SingletonDome04 getInstance(){
   
        return SingletClassInstance.instance;
    }
}

枚举实现单例模式

优点:

  • 实现简单.
  • 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!

缺点:无延迟加载.

package com.ahzy;
/** * 单例枚举式 * @author 晓宇码匠 * 优点:简单 * 缺点:没有延迟加载 */

public enum SingletonDome05 {
   
    //这个枚举元素,本身就是单例模式
    INSTANCE;
    //添加自己需要的操作
    public void singletonOperation() {
   
        
    }
}

测试

package com.ahzy;

public class Client {
   
    public static void main(String[] args) {
   
        SingletonDome01 s1 = SingletonDome01.getInstance(); 
        SingletonDome01 s2 = SingletonDome01.getInstance(); 
        
        System.out.println(s1);
        System.out.println(s2);
        
        System.out.println(SingletonDome05.INSTANCE==SingletonDome05.INSTANCE);
    }
}

结果:

com.ahzy.SingletonDome01@15db9742
com.ahzy.SingletonDome01@15db9742
true

破解单例

方法:反射和反序列化(不包含枚举式)
预防操作(这个一般会在开发jdk或一些jar包的时候会去用):

  • 反射可以破解上面几种(不包含枚举式)实现方式!(可以在构造方法中手动抛出异常控制)
  • 可以通过定义readResolve()防止获得不同对象。
  • 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
public class SingletonDemo01 implements Serializable {
   
    private static SingletonDemo01 s;

    private SingletonDemo01() throws Exception {
   
        if (s != null) {
   
            throw new Exception("只能创建一个对象");
            // 通过手动抛出异常,避免通过反射创建多个单例对象!
        }
    } // 私有化构造器

    public static synchronized SingletonDemo01 getInstance() throws Exception {
   
        if (s == null) {
   
            s = new SingletonDemo01();
        }
        return s;
    }

    // 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
    private Object readResolve() throws ObjectStreamException {
   
        return s;
    }
}

总结

  • 效率
模式 时间
饿汉式 22ms
静态内部类式 28ms
枚举式 32ms
双重检查锁式 65ms
懒汉式 636ms
  • 比较与特点
    主要:
    • 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
    • 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
    其他:
    • 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
    • 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
    • 枚举式(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞!)
  • 如何选用?
    单例对象 占用 资源 少,不需要 延时加载:枚举式 好于 饿汉式。
    单例对象 占用 资源 大,需要 延时加载: 静态内部类式 好于 懒汉式。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/106711.html原文链接:https://javaforall.net

(0)
上一篇 2021年7月21日 下午12:00
下一篇 2021年7月21日 下午1:00


相关推荐

  • 从零开始学 iOS 开发的15条建议「建议收藏」

    从零开始学 iOS 开发的15条建议「建议收藏」作者:清醒疯子利炳根授权本站转载。事情困难是事实,再困难的事还是要每天努力去做是更大的事实。因为我是一路自学过来的,并且公认没什么天赋的前提下,进步得不算太慢,所以有很多打算从零开始的朋友会问我,该怎么学iOS开发。跟粉丝群的朋友交流了一下,决定把之前的《18条建议》修改一下,这里统一回答一次。分三部分:第一部分:态度和电脑1、不要关注别人的学习经历,不要关注别人加薪经历

    2022年5月30日
    36
  • strncmp()函数用法及其详解

    strncmp()函数用法及其详解strncmp 函数 C 库函数 intstrncmp constchar str1 constchar str2 size tn 把 str1 和 str2 进行比较 最多比较前 n 个字节 声明 intstrncmp constchar str1 constchar str2 size tn 参数 str1 要进行比较的第一个字符串 st

    2026年3月18日
    3
  • accept函数的用法_requestscope用法

    accept函数的用法_requestscope用法这是在学《Windows网络编程(第二版)》第六章时制作的一个例子由于书中没有给出简洁的例子,本人在学这里时就费了很多时间。现在把完成的代码贴出来,供大家参考。下面包括了主程序部分,工作线程在(2)中贴出,由于代码太长。本程序在VS2003编译器编译运行。在6.0下可能需要稍加修改。#include#include#include#include    //微软扩展的类库

    2026年4月18日
    4
  • 融资135亿亏损20亿,智谱冲刺IPO

    融资135亿亏损20亿,智谱冲刺IPO

    2026年3月12日
    2
  • 常见应用层协议

    常见应用层协议常见应用层协议 1 超文本传输协议用于传输浏览器使用的普通文本 超文本 音频和视频等数据 详细情况请看 2 邮件协议在互联网中 电子邮件的传送是依靠这些协议完成的 详细情况请看 3 文件传输协议用来在客户机与服务器之间进行简单文件传输的协议 详细情况请看 4 域名解析协议万维网上作为域名和 IP 地址相互映射的一个分布式数据库 能够使用户更方便的访问互联网 详细情况请看 5

    2026年3月17日
    1
  • Java详解:淘宝秒杀脚本java

    Java详解:淘宝秒杀脚本java造成雪崩的真实场景1.4.1服务提供者不可用硬件故障:如网络故障、硬盘损坏等。程序的bug:如算法需要占用大量CPU的计算时间导致CPU使用率过高。缓存击穿:比如应用刚重启,短时间内缓存是失效的,导致大量请求直接访问到了数据库,数据库不堪重负,服务不可用。秒杀和大促:服务短时间承载不了那么多请求量。1.4.2重试加大流量用户连续重试:比如用户看到界面上没有响应,所以又操作了一遍,结果又增加了一倍请求量。程序重试机制:比如代码中有多次重试的逻辑,一次失

    2022年6月1日
    37

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号