设计模式之单例模式~

设计模式包含很多,但与面试相关的设计模式是单例模式,单例模式的写法有好几种,我们主要学习这三种—饿汉式单例,懒汉式单例、登记式单例,这篇文章我们主要学习饿汉式单例

单例模式:

满足要点:

私有构造
提供静态全局变量
提供专门访问静态全局变量的方法

饿汉式实现:

实现类:

import java.io.Serializable;


//1:饿汉式
public class Singleton1 implements Serializable {
    //私有构造
    private Singleton1(){
        System.out.println("private Singleton1");
    }

    //提供静态的全局变量
    private  static final Singleton1 INSTANCE= new Singleton1();//相对于懒汉式是提前创建的

    //提供专有的方法去获得静态变量
    public static Singleton1 getInstance(){
            return  INSTANCE;
    }
    
    public static void otherMethod(){
        System.out.println("otherMethod()");
    }
}

测试类:

public class Test_Singleton1 {
    public static void main(String[] args) {
    //触发当前类的加载链接--导致该类的初始化操作被创建,当我们调用otherMethod()方法时,该类已经被创建,因为在输出的内容中,包含该类私有构造中的内容
        Singleton1.otherMethod();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        //下面我们调用getInstance()方法时,得到的对象为上述已经创建好的对象
        System.out.println(Singleton1.getInstance());
        System.out.println(Singleton1.getInstance());
    }
}

输出:

private Singleton1
otherMethod()
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Singleton1@7ef20235
Singleton1@7ef20235

由于上述的单例我们实现的是Serializable接口,因此很容易被破坏,常见的破坏方式有以下几种:

反射破坏单例:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test_Singleton1 {
 		Singleton1.otherMethod();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println(Singleton1.getInstance());
        System.out.println(Singleton1.getInstance());//反射破坏单例
        reflection(Singleton1.class);

    }
    private static void reflection(Class<?> classz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    	//通过getDeclaredConstructor获得私有构造
        Constructor<?> constructor=classz.getDeclaredConstructor();
        //通过setAccessible使得私有的构造方法也可以被使用
        constructor.setAccessible(true);
        //使用newInstance创建新的对象
        System.out.println("反射创建实例:"+constructor.newInstance());
    }
}

输出:

private Singleton1
otherMethod()
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Singleton1@7ef20235
Singleton1@7ef20235
private Singleton1
反射创建实例:Singleton1@15aeb7ab

经过反射操作的Singleton1已经不是单例了,由于getInstance和反射创建的对象并不是同一个,一个类有两个对象,并不符合单例

newInstance()和new()区别:

创建对象的方式不同,newInstance()是实用类的加载机制,new()则是直接创建一个类newInstance创建类是这个类必须已经加载过且已经连接(Class.forName(“A”)这个过程)new创建类是则不需要这个类加载过

newInstance实际上是把new这个方式分解为两步,首先调用class的加载方法加载某个类,然后实例化,这样做的好处体现在我们可以在调用class的静态加载方法forName时获得更好的灵活性

newInstance 是弱类型(GC是回收对象的限制条件很低,容易被回收)、低效率、只能调用无参构造

new是强类型(GC不会自动回收,只有所有的指向对象的引用被移除是才会被回收,若对象生命周期已经结束,但引用没有被移除,经常会出现内存溢出)

预防反射破坏单例:

解决方法:

在私有构造中加入如下代码,当单例对象已经被创建过时,则直接抛出异常

if(INSTANCE!=null) {
            throw new RuntimeException("单例对象不能重复创建");
}

测试结果如下:

对象只被创建了一次,第二次通过反射创建对象时,直接抛出异常

在这里插入图片描述

反序列化破坏单例:

import java.io.*;
import java.lang.reflect.InvocationTargetException;

public class Test_Singleton1 {
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
        Singleton1.otherMethod();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println(Singleton1.getInstance());
        System.out.println(Singleton1.getInstance());
        
        //反序列化破坏单例
        serializable(Singleton1.getInstance());
    }
    private static void  serializable(Object instance) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        //创建输出流对象
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
        //通过writeObject将获取到的对象转变为字节流
        objectOutputStream.writeObject(instance);
        //创建文件输入流对象
        //toByteArray()方法是将一个ByteArrayOutputStream对象转换为byte字符数组返回
        ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        //通过readObject将转变后的字节流还原成对象
        System.out.println("反序列化创建实例:"+objectInputStream.readObject());
    }
}

输出:

这种反序列化的过程不仅可以产生新的对象,且并不实现构造方法

private Singleton1
otherMethod()
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Singleton1@7ef20235
Singleton1@7ef20235
反序列化创建实例:Singleton1@1a93a7ca

预防反序列化破坏单例:

解决办法为在Singleton1类中重写readResolve方法,使得它的返回值为初始创建的instance对象,而不是通过反序列化再创建新的对象

public Object readResolve(){
        return INSTANCE;
    }

Unsafe破坏单例:

 unsafe(Singleton1.class);
 private static void unsafe(Class<?> clazz) throws InstantiationException {
 	    Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
        System.out.println("Unsafe 创建实例:" + o);
}

Unsafe是JDK内置的一个类,它并不是Java标准类,一般的开发者不会涉及此类的开发,它不需要调用构造函数,且这种破坏方式目前没有解决的办法

枚举类实现饿汉式单例:

enum_Sington类:

public enum enum_Sington {
    INSTANCE;
    private enum_Sington(){
        System.out.println("private enum_Sington()");
    }
    public static enum_Sington getInstance(){
        return INSTANCE;
    }

    @Override
    public String toString() {
        return getClass().getName()+"@"+Integer.toHexString(hashCode());
    }
    public static void otherMethod(){
        System.out.println("otherMethod()");
    }
}

测试类:

import java.io.*;
import java.lang.reflect.InvocationTargetException;
public class Test_Sington2 {

    //单例模式-饿汉式
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException,IOException, ClassNotFoundException {
        enum_Sington.otherMethod();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println(enum_Sington.getInstance());
        System.out.println(enum_Sington.getInstance());

    }
}

输出:

private enum_Sington()
otherMethod()
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
enum_Sington@7ef20235
enum_Sington@7ef20235

然而对于枚举类,反序列化并不能将其破坏掉,验证如下:

在上述的测试类中加入反序列的代码,如下所示:

 serializable(enum_Sington.getInstance());
    private static void  serializable(Object instance) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
        //通过writeObject将获取到的对象转变为字节流
        objectOutputStream.writeObject(instance);
        ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        //通过readObject将转变后的字节流还原成对象
        System.out.println("反序列化创建实例:"+objectInputStream.readObject());
    }

输出:

private enum_Sington()
otherMethod()
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
enum_Sington@7ef20235
enum_Sington@7ef20235
反序列化创建实例:enum_Sington@7ef20235

通过输出结果,我们会发现,反序列化创建的对象和上述枚举类创建的对象是同一个

那么反射可以破坏吗?

验证如下:

reflection(enum_Sington.class);
private static void reflection(Class<?> classz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //通过getDeclaredConstructor获得私有构造
        Constructor<?> constructor=classz.getDeclaredConstructor();
        //通过setAccessible使得私有的构造方法也可以被使用
        constructor.setAccessible(true);
        System.out.println("反射创建实例:"+constructor.newInstance());
    }

在这里插入图片描述

newInstance实例化对象只能调用无参构造方法(如果重写了一个带参构造方法,想要使用newInstance,则必须指定一个无参构造方法,否则会报初始化错误)

在这里插入图片描述

懒汉式实现:

import java.io.Serializable;

public class Singleton3 implements Serializable {
    private Singleton3(){
        System.out.println("private Singleton3()");
    }
    private static Singleton3 INSTACE =null;//开始将其置为null,当要使用它时,才创建该对象
    public static Singleton3 getInstance() {
        if (INSTACE == null) {
            INSTACE = new Singleton3();
        }
        return INSTACE;
    }
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}
class Test_Singleton1 {
    public static void main(String[] args) {
    //INSTACE对象此时还不会被创建
        Singleton3.otherMethod();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        //当调用getISTACE方法时,INSTACE对象才会被创建
        System.out.println(Singleton3.getInstance());
        System.out.println(Singleton3.getInstance());
    }
}
otherMethod()
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
private Singleton3()
Singleton3@7ef20235
Singleton3@7ef20235

但懒汉式会出现一个问题,也就是不加线程的保护下,多个线程都可以调用getINSTACE方法,那么当多线程调用这个方法时,就会出现下述问题:

线程1首次调用getINSATCE方法,发现INSTACE是null没有被创建,因此进行创建,当线程1执行到if语句中的代码时,线程二也调用该方法,假设此时线程1还没有完成将刚创建好的对象赋值给INSTACE的这个操作,就会出现当线程2调用该方法时,INSTACE显示还是为null,它也会创建新的INSTACE对象,此时就不能被称为是单例了

在这里插入图片描述

这就是我们在多线程环境下执行单例存在的问题,那么该如何解决呢?

给getInstance方法加安全保护,也就是用sychronized进行修饰,其原理就是给该方法加锁

当加锁后的运行过程如下所示:

在这里插入图片描述
在这里插入图片描述

当线程2获得使用权后,进入该方法,发现INSTCE不为null,则不进行该对象的创建,直接返回前面线程1已经创建好的INSTCE对象,这样就能有效的避免多线程下单例模式的正确运行

但这样做的性能并不是最好的,原因是:我们加锁的目的就是为了防止INSTACE被多次创建,这样虽然能够解决首次仅由一个线程创建该对象,但当下次调用该方法的时候,加锁似乎没什么意义,因为对象已经被创建出来了

我们理想的目标是首次调用该方法加锁,而后续调用不加锁

优化方法——使用双检锁:

在这里插入图片描述

注意:

在这里插入图片描述

使用双检锁的INSTACE对象必须用volatile关键字修饰:主要利用内存屏障,保证有序性,在多线程下防止发生指令重排序

private static  volatile Singleton3 INSTACE =null;

内部类实现懒汉式单例:

发生在懒汉式中的多线程安全问题并没有出现在饿汉式实现单例中,原因是我们将其INSTACE对象的创建赋值给了static修饰的变量,而被static修饰的代码最终都会放在静态代码块中执行,而静态代码块中的线程安全并不需要我们去考虑,java虚拟机会帮我们保证其安全

那我们只需想办法将懒汉式中的INSTACE对象创建过程放在一个静态的代码块中即可,如下所示:

内部类也是符合懒汉式的特征,如果内部类没有被使用,那么类的加载链接初始化过程也不会发生,自然写在其中的INSTACE也不会被重复创建,那么什么时候会使用到它呢?

就是调用getInstace方法的时候,因此我们只需要将类的使用过程写在getInstace方法中

import java.io.Serializable;

public class Singleton3 implements Serializable {
    private Singleton3(){
        System.out.println("private Singleton3()");
    }
     private static class Holder {
        static Singleton3 INSTACE=new Singleton3();
    }
    public static Singleton3 getInstance(){
        return Holder.INSTACE;
    }
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}
class Test_Singleton1 {
    public static void main(String[] args) {
        //触发当前类的加载链接--导致该类的初始化操作被创建,当我们调用otherMethod()方法时,该类已经被创建,因为在输出的内容中,包含该类私有构造中的内容
        Singleton3.otherMethod();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        //下面我们调用getInstance()方法时,得到的对象为上述已经创建好的对象
        System.out.println(Singleton3.getInstance());
        System.out.println(Singleton3.getInstance());
    }
}

上述使用内部类的懒汉式,也正是我们所推荐的,不仅符合懒汉式的特征,而且可以保证线程安全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/1300.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

改进YOLO系列 | CVPR2023最新 PConv | 提供 YOLOv5 / YOLOv7 / YOLOv7-tiny 模型 YAML 文件

DWConv是Conv的一种流行变体,已被广泛用作许多神经网络的关键构建块。对于输入 I ∈ R c h w I \in R^{c \times h \times w} I∈

用chatgpt写insar地质灾害的论文,重复率只有1.8%,chatgpt4.0写论文不是梦

突发奇想&#xff0c;想用chatgpt写一篇论文&#xff0c;并看看查重率&#xff0c;结果很惊艳&#xff0c;说明是确实可行的&#xff0c;请看下图。 下面是完整的文字内容。 InSAR (Interferometric Synthetic Aperture Radar) 地质灾害监测技术是一种基于合成孔径雷达…

GPT-4,终于来了!

就在昨天凌晨&#xff0c;OpenAI发布了多模态预训练大模型GPT-4。 这不昨天一觉醒来&#xff0c;GPT-4都快刷屏了&#xff0c;不管是在朋友圈还是网络上都看到了很多信息和文章。 GPT是Generative Pre-trained Transformer的缩写&#xff0c;也即生成型预训练变换模型的意思。…

jupyter的安装和使用

目录 ❤ Jupyter Notebook是什么&#xff1f; notebook jupyter 简介 notebook jupyter 组成 网页应用 文档 主要特点 ❤ jupyter notebook的安装 notebook jupyter 安装有两种途径 1.通过Anaconda进行安装 2.通过pip进行安装 启动jupyter notebook ❤ jupyter …

5G(NR)信道带宽和发射带宽---频率资源

前言 查看此文之前建议先看看这篇 5G(NR)频率资源划分_nr运营商频段划分_达帮主的博客-CSDN博客NR频率有上面几个划分 &#xff0c;可以使用低于1GHz的频端&#xff0c;既可以使用高于30GHz高频端。使用频端高于30GHz那我们称之为高频或者毫米波。使用毫米波是5G网络区别于4G…

蓝桥冲刺31天之317

在这个时代&#xff0c;我们总是在比较&#xff0c;觉得自己不够好 其实不必羡慕别人的闪光点 每个人都是属于自己的限量版 做你喜欢并且擅长的事&#xff0c;做到极致 自然会找到自己独一无二的价值 鸟不跟鱼比游泳&#xff0c;鱼不跟鸟比飞翔 你我各有所长 A&#xff1a;组队…

【数学基础】你还不理解最大似然估计吗?一篇文章带你快速了解掌握

&#x1f4da;引言 &#x1f64b;‍♂️作者简介&#xff1a;生鱼同学&#xff0c;大数据科学与技术专业硕士在读&#x1f468;‍&#x1f393;&#xff0c;曾获得华为杯数学建模国家二等奖&#x1f3c6;&#xff0c;MathorCup 数学建模竞赛国家二等奖&#x1f3c5;&#xff0c…

JAVA并发编程之锁

1、乐观锁和悲观锁 1.1、悲观锁 认为自己在使用数据的时候一定有别的线程来修改数据&#xff0c;因此在获取数据的时候会加锁&#xff0c;确保数据不会别的线程修改。synchronized关键字和Lock的实现类都是悲观锁。适合写操作多的场景&#xff0c;先加锁可以保证写操作时数据…

leetcode刷题之回文链表

目录 做题思路 代码实现 1.找到链表的中间节点 2.反转中间节点之后的链表 3.判断倒置的后半部分的链表是否等于前半部分的链表 整体代码展示 总结&#xff1a; 这里是题目链接。 这道题目的意思是&#xff1a;判断该链表中后半部分倒置是否跟前半部分相同&#xff0c;如…

java 每日一练 (8)

文章目录1. 单选题2. 编程题1. 单选题 1. 下列选项中关于 java 中 super 关键字的说法正确的是 () A&#xff1a; super 关键字是在子类对象内部指代父类对象的引用. B &#xff1a; super 关键字不仅可以指代子类的直接父类&#xff0c;还可以直接指代父类的父类. C &#…

API-Server的监听器Controller的List分页失效

前言 最近做项目&#xff0c;还是K8S的插件监听器&#xff08;理论上插件都是通过API-server通信&#xff09;&#xff0c;官方的不同写法居然都能出现争议&#xff0c;争议点就是对API-Server的请求的耗时&#xff0c;说是会影响API-Server。实际上通过源码分析两着有差别&am…

<script>标签在html中书写位置-课后程序(JavaScript前端开发案例教程-黑马程序员编著-第1章-课后作业)

【案例1-1】 <script>标签在html中书写位置 一、案例描述 考核知识点 <script>标签可以放在html中什么位置 练习目标 掌握<script>标签放在页面中不同位置的区别。 需求分析 将JavaScript标识放置<Head>... </Head>在头部之间&#xff0c;使之…

LInux指令之文件目录类

文章目录一、帮助指令二、文件目录类ls指令cd指令 &#xff08;切换目录&#xff09;mkdir指令&#xff08;创建目录&#xff09;rmdir指令&#xff08;删除目录&#xff09;touch指令&#xff08;创建空文件&#xff09;cp指令(拷贝文件)rm指令mv指令cat指令(查看)more指令les…

GEE:计算1990-2021年的指数最大值和最小值,并根据最大最小值对每一副影像归一化

本文记录了在GEE平台上计算影像集合中所有像素的最大值和最小值。并且根据该最大最小值对所有影像进行最大最小值归一化。以SAVI为例,记录了主要函数的使用方法和代码。 结果如图所示, 文章目录 一、计算每一副影像的最大值或者最小值,并将最值保存在 List 中二、计算 Lis…

AD域安全攻防实践(附攻防矩阵图)

以域控为基础架构&#xff0c;通过域控实现对用户和计算机资源的统一管理&#xff0c;带来便利的同时也成为了最受攻击者重点攻击的集权系统。 01、攻击篇 针对域控的攻击技术&#xff0c;在Windows通用攻击技术的基础上自成一套技术体系&#xff0c;将AD域攻防分为信息收集、权…

安装Docker

Docker分为CE和EE两大版本。CE即社区版&#xff08;免费&#xff0c;支持周期7个月&#xff09;&#xff0c;EE即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道。 官方网站上有各种环境…

Nacos 注册中心 - 健康检查机制源码

目录 1. 健康检查介绍 2. 客户端健康检查 2.1 临时实例的健康检查 2.2 永久实例的健康检查 3. 服务端健康检查 3.1 临时实例的健康检查 3.2 永久实例服务端健康检查 1. 健康检查介绍 当一个服务实例注册到 Nacos 中后&#xff0c;其他服务就可以从 Nacos 中查询出该服务…

LeetCode234_234. 回文链表

LeetCode234_234. 回文链表 一、描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&…

Day920.结构化日志业务审计日志 -SpringBoot与K8s云原生微服务实践

结构化日志&业务审计日志 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于结构化日志&业务审计日志的内容。 1、什么是结构化日志 结构化日志&#xff08;Structured Logging&#xff09;是一种将日志信息组织为结构化数据的技术。 传统的日志通常是一些文…

UE实现建筑分层抽屉展示效果

文章目录 1.实现目标2.实现过程2.1 基础设置2.2 核心函数3.参考资料1.实现目标 使用时间轴对建筑楼层的位置偏移进行控制,实现分层抽屉的动画展示效果。 2.实现过程 建筑抽屉的实现原理比较简单,即对Actor的位置进行偏移,计算并更新其世界位置即可。这里还是基于ArchVizExp…