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/106710.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • hpe服务器安装操作系统_hp 服务器 电话

    hpe服务器安装操作系统_hp 服务器 电话服务器安装操作系统才能对服务器进行很好的操作,但是新购买的服务器要如何安装操作系统呢?其实服务器系统安装通常有两种方法:用服务器引导光盘引导安装(HPProliant系列服务器100系列设备随机光盘SupportCD没有引导安装操作系统的功能)、用操作系统光盘加载阵列卡驱动引导安装。  用操作系统光盘直接安装操作系统  通常情况下重新安装操作系统,数据盘的重要数据需要保

    2022年10月4日
    3
  • 较完整的 bean生命周期[通俗易懂]

    较完整的 bean生命周期[通俗易懂]首先需要说明的是,Bean的生命周期主要指的是singletonbean,标签的scope默认就是singleton。对prototypebean来说,当用户getBean获得prototypebean的实例后,IOC容器就不再对当前实例进行管理,而是把管理权交由用户,此后再getBean生成的是新的实例。普通JavaBean和SpringBean普通java对象就是new出来,然后不再使用的时候通过垃圾回收机制进行回收; 而springBean是由spring容器来控制的,并且..

    2022年9月16日
    1
  • STMS中QAS->PRD通信不畅[通俗易懂]

    STMS中QAS->PRD通信不畅[通俗易懂]现象:DEV->QAS->PRD的传输域配置中,DEV->QAS正常,PRD也可以检测到DEV释放的请求,但是就是无法将请求内容添加(闪电标识)至PRDbuffer。将请求文件copy至PRD的trans目录下,上载成功,导入成功。1.怀疑,PRD的\usr\sap的权限,确认也有写入权限2.怀疑QAS和PRD的通信,可以ping通3.STMS传

    2022年6月28日
    30
  • 设备树传参中的platform device的匹配

    设备树传参中的platform device的匹配在高版本的Linux内核中使用了设备树进行传参,之前购买一块nanopi的板子使用的是linux4.11.2版本的内核(使用的友善之臂的Mainlinelinux)就是这种情况,并且使用设备树传参过后,原来硬编码在mach-xxx.c文件中的platformdevice全部都放入了设备树中,而原来使用name进行platformdevice和driver进行匹配的方式也发生了变化。以

    2022年7月24日
    16
  • 为UWP应用开启回环访问权限「建议收藏」

    为UWP应用开启回环访问权限

    2022年3月13日
    181
  • 前端框架AdminLTE

    前端框架AdminLTE作为CMDB资产管理项目,必须有一个丰富、直观、酷炫的前端页面。适合运维平台的前端框架有很多,开源的也不少,这里选用的是AdminLTE。AdminLTE托管在GitHub上,可以通过下面的地址下载:https://github.com/almasaeed2010/AdminLTE/releasesAdminLTE自带JQuery和Bootstrap3框架,无需另外下载。AdminLTE自带多种配…

    2022年7月27日
    17

发表回复

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

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