单例模式的运用

news/2024/11/29 20:44:49/

文章目录

  • 单例模式的运用
    • 一、介绍
    • 二、饿汉式
      • 2.1 静态变量方式
      • 2.2 静态代码块方式
      • 2.3 枚举方式
    • 三、懒汉式
      • 3.1 线程不安全方式
      • 3.2 线程安全方式
      • 3.3 双重检查锁方式
      • 3.4 静态内部类方式
    • 四、破坏单例模式
      • 4.1 序列化破坏
      • 4.2 序列化破坏解决办法
      • 4.3 反射破坏
      • 4.4 反射破坏解决办法

单例模式的运用

一、介绍

单例模式:属于创建型模式,涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例设计模式分类两种:

  • 饿汉式:类加载就会导致该单实例对象被创建。

  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建。

二、饿汉式

2.1 静态变量方式

该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费

/*** 静态变量创建类的对象*/
public class Singleton {// 私有类的无参构造private Singleton() {}// 给本类创建一个新的对象,并用私有化无法访问,使用静态关键字static来修饰private static Singleton instance = new Singleton();// 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法public static Singleton getInstance(){return instance;}
}public static void main(String[] args) {// 重复调用Singletion类的getInstance方法Singleton instance1 = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();// 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式System.out.println(instance1 == instance2);//true
}

2.2 静态代码块方式

该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的静态变量方式基本上一样,当然该方式也存在内存浪费问题。

/*** 静态变量创建类的对象*/
public class Singleton {// 私有的构造方法private Singleton(){}// 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰private static Singleton instance;//null// 在静态代码块中进行赋值,创建Singleton的对象static{instance = new Singleton();}// 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法public static Singleton getInstance() {return instance;}
}public static void main(String[] args) {// 重复调用Singletion类的getInstance方法Singleton instance1 = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();// 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式System.out.println(instance1 == instance2);//true
}

2.3 枚举方式

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会加载一次,枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

/*
* 枚举方式
* 枚举方式属于恶汉式方式
**/
public enum Singleton {INSTANCE;
}public static void main(String[] args) {// 重复调用Singletion枚举类的INSTANCESingleton instance1 = Singleton.INSTANCE;Singleton instance2 = Singleton.INSTANCE;System.out.println(instance1 == instance2);
}

三、懒汉式

3.1 线程不安全方式

该方式在成员位置声明Singleton类型的静态变量,当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,在多线程环境,出现线程安全问题

/*** 线程不安全*/ 
public class Singleton {// 私有的构造方法private Singleton(){}// 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰private static Singleton instance;//null// 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法public static Singleton getInstance() {// 判断instance是否为null,如果为null,说明还没有创建Singleton类的对象// 如果不为空,说明已将创建过,返回对象即可if(instance == null){instance = new Singleton();}return instance;}
}public static void main(String[] args) {// 重复调用Singletion类的getInstance方法Singleton instance1 = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();// 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式System.out.println(instance1 == instance2);//true
}

3.2 线程安全方式

该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在该问题了。

/*** 线程安全*/ 
public class Singleton {// 私有的构造方法private Singleton(){}// 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰private static Singleton instance;//null// 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法public static synchronized Singleton getInstance() {// 判断instance是否为null,如果为null,说明还没有创建Singleton类的对象// 如果不为空,说明已将创建过,返回对象即可if(instance == null){// 加锁前,线程1、2都进入到该位置,此时就会出现创建多个对象的情况// 加锁后,线程1进入,线程2会在外面等待线程1运行完毕,释放锁后才继续执行instance = new Singleton();}return instance;}
}public static void main(String[] args) {// 重复调用Singletion类的getInstance方法Singleton instance1 = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();// 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式System.out.println(instance1 == instance2);//true
}

3.3 双重检查锁方式

该方式对懒汉模式中加锁的问题做出变化,对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机

多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性

/*
* 双重检查锁
* 添加 volatile关键字之后的双重检查锁模式是一种比较好的单例
* 实现模式,能够保证在多线程的情况下线程安全也不会有性能问题
**/
public class Singleton {// 私有的构造方法private Singleton(){}// 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰private static volatile Singleton instance;//null// 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法public static Singleton getInstance() {// 第一次判断,判断instance是否为null,如果为null,说明还没有创建Singleton类的对象,不需要抢占锁// 如果不为空,说明已将创建过,返回对象即可,读操作直接返回,没有加锁if(instance == null){// 第二次判断,写操作需要加锁synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}public static void main(String[] args) {// 重复调用Singletion类的getInstance方法Singleton instance1 = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();// 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式System.out.println(instance1 == instance2);//true
}

3.4 静态内部类方式

该方式的实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序

静态内部类方式在没有加任何锁的情况下,保证线程安全,并且没有任何性能影响和空间的浪费

/*
* 静态内部类方式
* 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
**/
public class Singleton {// 私有的构造方法private Singleton(){}//定义一个静态内部类private static class SingletonHolder {//在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰private static final Singleton INSTANCE = new Singleton();}// 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法public static Singleton getInstance() {// 内部类直接调用对象return SingletonHolder.INSTANCE;}
}public static void main(String[] args) {// 重复调用Singletion类的getInstance方法Singleton instance1 = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();// 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式System.out.println(instance1 == instance2);//true
}

四、破坏单例模式

4.1 序列化破坏

代码运行结果是false,表明序列化和反序列化已经破坏了单例设计模式。

对象里面的值是一样的,只不过对象的引用不一样,相当于是两个对象,涉及到了深拷贝

/*** 静态内部类方式的单列模式*/
public class Singleton implements Serializable {// 私有的构造方法private Singleton(){}//定义一个静态内部类private static class SingletonHolder {//在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰private static final Singleton INSTANCE = new Singleton();}// 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法public static Singleton getInstance() {// 内部类直接调用对象return SingletonHolder.INSTANCE;}
}public class Client {public static void main(String[] args) throws Exception {
//        writeObjectToFile();Singleton s1 = readObjectFromFile();Singleton s2 = readObjectFromFile();System.out.println(s1 == s2);}// 使用序列化向文件中写数据(对象)private static void writeObjectToFile() throws IOException {// 获取Singleton对象Singleton instance = Singleton.getInstance();// 创建对象输出流对象ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));// 将对象写入objectOutputStream.writeObject(instance);// 释放资源objectOutputStream.close();}// 使用反序列化读取文件的数据(对象)private static Singleton readObjectFromFile() throws Exception {// 创建对象输入流对象ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\a.txt"));// 读取对象Singleton instance = (Singleton) objectInputStream.readObject();System.out.println(instance);// 释放资源objectInputStream.close();return instance;}
}

4.2 序列化破坏解决办法

想要解决序列化、反序列化对单例模式的破坏,我们需要在Singleton类中添加readResolve()方法,在其被反序列化时调用,返回这个方法的值(对象)。 原理是在输入流对象的readObject方法的底层实现中,如果发现输入的是对象类型, 则会调用readOrdinaryObject方法,该方法中会自动执行hasReadResolveMethod方法,来判断类中是否有readResolve方法,如果有,则会去执行invokeReadResolve方法来获取类中专门提供的实例,从而解决反序列化破坏单例模式的问题。

// 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
public Object readResolve(){return SingletonHolder.INSTANCE;
}

4.3 反射破坏

代码运行结果是false,表明反射已经破坏了单例设计模式。

/*** 静态内部类方式的单列模式*/
public class Singleton implements Serializable {// 私有的构造方法private Singleton(){}//定义一个静态内部类private static class SingletonHolder {//在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰private static final Singleton INSTANCE = new Singleton();}// 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法public static Singleton getInstance() {// 内部类直接调用对象return SingletonHolder.INSTANCE;}
}public class Client {public static void main(String[] args) throws Exception {// 获取Singleton字节码对象Class<Singleton> singletonClass = Singleton.class;// 获取无参构造方法对象Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();// 取消访问检查declaredConstructor.setAccessible(true);// 创建Singleton对象Singleton s1 = (Singleton) declaredConstructor.newInstance();Singleton s2 = (Singleton) declaredConstructor.newInstance();// 结果返回false,说明反射破坏了单例模式System.out.println(s1 == s2);}
}

4.4 反射破坏解决办法

当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此次操作。

/*** 静态内部类方式的单列模式*/
public class Singleton {// 定义一个flag判断是否已经创建过对象private static boolean flag = false;// 给私有构造方法添加锁,预防线程安全问题private Singleton(){synchronized(Singleton.class){// flag为false则跳过,直接正常创建,若flag为true,说明已经创建过,抛出异常if(flag){throw new RuntimeException("不能创建多个对象");}// 第一次创建后,应该将flag设置为trueflag = true;}}//定义一个静态内部类private static class SingletonHolder {//在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰private static final Singleton INSTANCE = new Singleton();}// 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法public static Singleton getInstance() {// 内部类直接调用对象return SingletonHolder.INSTANCE;}
}

记录每一个学习瞬间


http://www.ppmy.cn/news/185510.html

相关文章

计算机专业小米笔记本推荐,小米笔记本电脑怎么样有哪些型号,哪款性价比高?...

在这个飞速发展的社会上&#xff0c;最耀眼的当属IT市场了&#xff0c;在年轻人的眼中&#xff0c;拿出手的东西都要看颜值&#xff0c;因此在为上学做准备时&#xff0c;买的设备都要有着好看的外观&#xff0c;所以一台轻薄靓丽的笔记本不仅能够吸引更多人的眼球&#xff0c;…

2020笔记本性价比之王_2020轻薄本性价比之王_2020轻薄本哪款好

随着科技的进步&#xff0c;如今笔记本的功能愈发强大&#xff0c;接听电话、开展商务会议等&#xff0c;因此现在人们对于笔记本电脑的依赖程度愈发明显。厚重的商务本和游戏本显然不适合随身携带&#xff0c;因此轻薄本的出现受到白领们的广泛吹捧&#xff0c;那么2020哪款轻…

日志处理小demo

from pyflink.datastream import StreamExecutionEnvironment from pyflink.datastream.functions import MapFunction, RuntimeContext, KeyedProcessFunction import re import redis # 创建 StreamExecutionEnvironment 对象 env StreamExecutionEnvironment.get_executi…

如何使用宝塔面板搭建网站(最后一步!上传文件至宝塔面板)

这里就是我们搭建宝塔面板的最后一步了&#xff0c;把我们的PHP文件上传到宝塔面板上&#xff0c;就可以通过因特尔网络访问我们的网址了。 第一步&#xff1a;基础安装 必须是Linux服务器 不支持window服务器 最低要求配置1核1G当然再低些也能运行但是不建议 要求服务器环…

PostgreSql根据给的时间范围统计15分钟粒度、小时粒度、天粒度、周粒度、月粒度工单

PostgreSql根据给的时间范围统计15分钟粒度、小时粒度、天粒度、周粒度、月粒度工单 说明实现15分钟粒度工单统计小时粒度工单统计天粒度工单统计周粒度工单统计月粒度工单统计 说明 项目有个需求是统计故障工单每15分钟、每小时、每天、每周和每月共有多少工单。 这里先做个笔…

2021年全国A级景区矢量分布数据(11969条)

一、景区定义 旅游景区是以旅游及其相关活动为主要功能或主要功能之一的空间或地域。本标准中旅游景区是指具有参观游览、休闲度假、康乐健身等功能&#xff0c;具备相应旅游服务设施并提供相应旅游服务的独立管理区。该管理区应有统一的经营管理机构和明确的地域范围。包括风景…

Unity替换启动Logo动画

前言 最近的项目中&#xff0c;由于Android中集成了Unity场景&#xff0c;每次进入打包好的Unity项目时都会出现Unity的标志性动画&#xff0c;让用户处于等待状态&#xff0c;体验感比较差&#xff1b;于是便在产品经理的要求下将Unity的启动Logo换成公司的宣传动画 目前的解…

3D建模,游戏建模和渲染极力推荐的5款笔记本电脑

如果想要一台用于3D建模&#xff0c;游戏建模和渲染的顶级笔记本电脑&#xff0c;这里有一些具有卓越性能的优秀笔记本电脑。 1. 华硕 ZenBook Pro Duo 满足您所有渲染和建模需求的高级工作站笔记本电脑 超快、健壮&#xff0c;将有助于制作出色的动画。 4K 超高清屏幕带来绚…