当前位置: 首页 > news >正文

设计模式--单例模式(懒汉、饿汉)

单例模式

  • 掌握五种单例模式的实现方式
  • 理解为何 DCL 实现时要使用 volatile 修饰静态变量
  • 了解 jdk 中用到单例的场景

一、饿汉式

public class Singleton1 implements Serializable {//实现了Serializable(转成字节保存在磁盘或是用于网络传输,都要实现它)
    //1.构造私有
    private Singleton1() {
        System.out.println("初始化啦");
    }

    //2.静态的成员变量,类型为这个单例类型
    private static final Singleton1 INSTANCE = new Singleton1();//内部才能new

    //3.静态方法,返回实例
    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    //4.用于测试对象在类初始化时便创建了(在测试类先调用这个方法,在getInstance)
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

在这里插入图片描述

反射破坏单例预防

//在refection方法中传入单例类,可以通过反射得到私有构造器并执行,得到新的对象,不再是单例
    private static void reflection(Class<?> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        System.out.println("反射创建实例:" + constructor.newInstance());
    }

在这里插入图片描述

解决方法,在私有构造器中添加代码
if (INSTANCE != null) { throw new RuntimeException("单例对象不能重复创建"); }

反序列化破坏单例预防

//此方法参数为单例对象
 private static void serializable(Object instance) throws IOException, ClassNotFoundException {
        //先把对象变成字节流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(instance);
        //再把字节流还原成对象,造出的新对象不走构造方法的
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        System.out.println("反序列化创建实例:" + ois.readObject());
    }

在这里插入图片描述

解决方法
在单例类中添加犯法readResolve方法,返回Instance
详细讲解参考这篇文章

完整版实现

public class Singleton1 implements Serializable {//实现了Serializable(转成字节保存在磁盘或是用于网络传输,都要实现它)
    //1.构造私有
    private Singleton1() {
        System.out.println("初始化啦");
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("private Singleton1()");
    }

    //2.静态的成员变量,类型为这个单例类型
    private static final Singleton1 INSTANCE = new Singleton1();//内部才能new

    //3.静态方法,返回实例
    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    //4.用于测试对象在类初始化时便创建了
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }

    //5.预防反序列化的破坏
    public Object readResolve() {
        return INSTANCE;
    }
}

二、枚举饿汉式

枚举类

enum Sex {
    MALE, FEMALE;
}
//编译之后的样子
/*final class Sex extends Enum<Sex> {
    public static final Sex MALE;
    public static final Sex FEMALE;

    private Sex(String name, int ordinal) {
        super(name, ordinal);
    }
//类一加载/初始化,这两个对象便被创建出来
    static {
        MALE = new Sex("MALE", 0);
        FEMALE = new Sex("FEMALE", 1);
        $VALUES = values();
    }

    private static final Sex[] $VALUES;

    private static Sex[] $values() {
        return new Sex[]{MALE, FEMALE};
    }

    public static Sex[] values() {
        return $VALUES.clone();
    }

    public static Sex valueOf(String value) {
        return Enum.valueOf(Sex.class, value);
    }
}*/

实现

public enum Singleton2 {
    INSTANCE;

    //枚举类的构造默认就是private,不写也可
    private Singleton2() {
        System.out.println("private Singleton2()");
    }

    @Override
    //重写tostring,打印下hashcode
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    //不提供这个方法也能用,因为枚举变量时公共的(直接Singleton2.INSTANCE)
    public static Singleton2 getInstance() {
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}
  • 枚举饿汉式能天然防止反射、反序列化破坏单例(内部已经做好预防)

三、懒汉式

public class Singleton3 implements Serializable {
    private Singleton3() {
        System.out.println("private Singleton3()");
    }

    private static Singleton3 INSTANCE = null;

    // 静态方法,Singleton3.class作为锁对象
    public static synchronized Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }

}

懒汉式调用otherMethod方法时,无事发生,构造器并未执行
在这里插入图片描述

弊端
其实只有首次创建单例对象时才需要同步,之后的线程在进入方法都是直接返回第一个线程创建好的对象;但该代码实际上每次调用都会同步
我们希望只有首次创建创建单例对象才调用,后续在调用方法便不要synchronized的保护

四、双检锁懒汉式

两次检查null,=>双检

public class Singleton4 implements Serializable {
    private Singleton4() {
        System.out.println("private Singleton4()");
    }

    private static volatile Singleton4 INSTANCE = null; //volatile关键字 解决共享变量的可见性,有序性

    public static Singleton4 getInstance() {
    	//第一个if判断时提高性能的,只有首次创建单例对象时才需要同步,后面直接返回第一个线程创建好的对象
        if (INSTANCE == null) {
            synchronized (Singleton4.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton4();
                }
            }
        }
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

为何必须加 volatile:
反编译这个单例类,观察getInstance方法
在这里插入图片描述

如果先执行给INSTANCE赋值(即给他一个引用地址,指向单例对象,此时单例对象还没有完成赋值),INSTANCE不为bull,变直接执行return了,但这是个没有完成构造赋值的对象,问题便出现了
在这里插入图片描述

  • INSTANCE = new Singleton4() 不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造(单线程下没影响,多线程可能会)

  • 如果线程1 先执行了赋值,线程2 执行到第一个 INSTANCE == null 时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象

volatile修饰的变量,赋值的时候,会在变量的赋值语句之后加上一个内存屏障;以阻止之前的赋值语句越过屏障,在他之后(简单来说就是防止指令重排序优化)
在这里插入图片描述


思考

饿汉式为何不需要考虑多线程下对象(重复)创建的问题
private static final Singleton1 INSTANCE = new Singleton1();

  • 饿汉式对象创建是赋值给了静态变量(这个操作最终会放在静态代码块里执行,jvm虚拟机会保证静态代码块执行的线程安全)
  • 枚举饿汉式中枚举变量的创建也是在静态代码块中执行的

五、内部类懒汉式

根据把对象的创建放入静态代码块便是线程安全的思路,得到这种方式

避免了双检锁的缺点

public class Singleton5 implements Serializable {
    private Singleton5() {
        System.out.println("private Singleton5()");
    }

	//静态内部类,可以访问外部的私有变量
	//没用到内部类时不会触发类的加载、链接、初始化
    private static class Holder {
    	//内部类的静态变量(这个操作最终会放在静态代码块里执行,jvm虚拟机会保证静态代码块执行的线程安全)
    	//只执行一次
        static Singleton5 INSTANCE = new Singleton5();
    }
	//
    public static Singleton5 getInstance() {
        return Holder.INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

在这里插入图片描述

六、JDK 中单例的体现

  • Runtime 体现了饿汉式单例
    在这里插入图片描述

  • Console 体现了双检锁懒汉式单例
    在这里插入图片描述

  • Collections 中的 EmptyNavigableSet 内部类懒汉式单例

  • 在这里插入图片描述

  • ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
    在这里插入图片描述

  • Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例
    在这里插入图片描述

相关文章:

  • 20个Java小项目,献给嗜学如狂的人,拿来练练手
  • GFS文件分布式系统概述与部署
  • zabbix监控基本概念和部署
  • 灵性图书馆:好书推荐-《情绪的惊人力量》
  • Google Earth Engine(GEE)——MODIS/061/MOD09GA影像计算NDVI并导出结果并UI可视化批量导出(含错误提示)
  • 【MC教程】iPad启动Java版mc(无需越狱)(保姆级?) Jitterbug启动iOS我的世界Java版启动器 PojavLauncher
  • 记SpringBoot拦截器报错getWriter() has already been called for this response
  • c++ boost库
  • pandas使用groupby函数基于指定分组变量对dataframe数据进行分组、使用first函数获取每个分组数据中每个分组的第一个样本数据
  • if选择结构分析
  • 【线性代数】MIT Linear Algebra Lecture 2: Elimination with matrices
  • 面试-测试软件Selenium
  • python--转换wrf输出的风场数据为网页可视化的json格式
  • 单细胞测序原理10X UMI Barcode
  • Rust-FFI复杂参数传递处理方式2--字符和字符串类型
  • Apache Spark Streaming 使用实例
  • Bytom交易说明(账户管理模式)
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • ECS应用管理最佳实践
  • Idea+maven+scala构建包并在spark on yarn 运行
  • JavaScript新鲜事·第5期
  • Java比较器对数组,集合排序
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • 对象引论
  • 聊聊directory traversal attack
  • 马上搞懂 GeoJSON
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 前端相关框架总和
  • 前嗅ForeSpider中数据浏览界面介绍
  • 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
  • 使用Swoole加速Laravel(正式环境中)
  • 1.Ext JS 建立web开发工程
  • kubernetes资源对象--ingress
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • $.ajax,axios,fetch三种ajax请求的区别
  • (07)Hive——窗口函数详解
  • (2)(2.10) LTM telemetry
  • (pojstep1.1.2)2654(直叙式模拟)
  • (webRTC、RecordRTC):navigator.mediaDevices undefined
  • (阿里云万网)-域名注册购买实名流程
  • (二)JAVA使用POI操作excel
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (转)德国人的记事本
  • .Net 8.0 新的变化
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .Net MVC + EF搭建学生管理系统
  • .net 按比例显示图片的缩略图
  • .Net6支持的操作系统版本(.net8已来,你还在用.netframework4.5吗)
  • .net生成的类,跨工程调用显示注释
  • .net下简单快捷的数值高低位切换
  • .NET正则基础之——正则委托
  • 。Net下Windows服务程序开发疑惑