Java 23种设计模式(2.创建者模式-单例设计模式)

news/2024/11/29 3:41:16/

1. 创建者模式

创建型模式分为:

  1. 单例模式
  2. 工厂方法模式
  3. 抽象工程模式
  4. 原型模式
  5. 建造者模式

什么是创建者模式?

创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节

2.单例设计模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

1.什么场景下使用?
写个工具箱窗口,如果点击多次弹出多个窗口,这不是我们想看的。我们希望点击一次出现一个窗口,这其实就是单例模式
2.如何解决问题?
让一个全局变量被一个对象那个访问,让类保存唯一的实例
在这里插入图片描述

2.1 单例模式的结构

单例模式的主要有以下角色:

  • 单例类:只能创建一个实例的类
  • 访问类:使用单例类

程序类图:
在这里插入图片描述

2.2 单例模式的实现

单例设计模式分类两种:
饿汉式类加载就会导致该单实例对象被创建
懒汉式类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

1)饿汉式-方式1(静态变量方式)

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

/*** @Description:*      饿汉式: 静态成员变量*/
public class Singleton {//1,私有构造方法private Singleton() {}//2,在本类中创建本类对象private static Singleton instance = new Singleton();//3,提供一个公共的访问方式,让外界获取该对象public static Singleton getInstance() {return instance;}
}

Test:

public class Client {public static void main(String[] args) {//创建Singletion类的对象Singleton instance = Singleton.getInstance();Singleton instance1 = Singleton.getInstance();//判断获取到的两个是否是同一个对象System.out.println(instance == instance1);}
}
true

2)饿汉式-方式2(静态代码块方式)

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

/**
* 恶汉式
* 在静态代码块中创建该类对象
*/
public class Singleton {//私有构造方法private Singleton() {}//声明Singleton类型的变量private static Singleton instance; //null//在静态代码块中进行赋值static {instance = new Singleton();}//对外提供获取该类对象的方法public static Singleton getInstance() {return instance;}
}

Test:

public class Client {public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton instance1 = Singleton.getInstance();//判断两次获取到的Singleton对象是否是同一个对象System.out.println(instance == instance1);}
}
true

3)懒汉式-方式1(线程不安全)

从下面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。

/**
* 懒汉式
* 线程不安全
*/
public class Singleton {//私有构造方法private Singleton() {}//声明Singleton类型的变量instanceprivate static Singleton instance; //只是声明一个该类型的变量,并没有进行赋值//对外提供访问方式public static Singleton getInstance() {//判断instance是否为null,如果为null,说明还没有创建Singleton类的对象//如果没有,创建一个并返回,如果有,直接返回if(instance == null) {//线程1等待,线程2获取到cpu的执行权,也会进入到该判断里面instance = new Singleton();}return instance;}
}

Test:

public class Client {public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton instance1 = Singleton.getInstance();//判断两次获取到的Singleton对象是否是同一个对象System.out.println(instance == instance1);}
}
true

4)懒汉式-方式2(线程安全)

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

/**
* 懒汉式
* 线程安全
*/
public class Singleton {//私有构造方法private Singleton() {}//在成员位置创建该类的对象private static Singleton instance;//对外提供静态方法获取该对象public static synchronized Singleton getInstance() {if(instance == null) {instance = new Singleton();}return instance;}
}

5)懒汉式-方式3(双重检查锁)

对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式

/**
* 双重检查方式
*/
public class Singleton {//私有构造方法private Singleton() {}//声明Singleton类型的变量private static volatile Singleton instance;//对外提供公共的访问方式public static Singleton getInstance() {//第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象if(instance == null) {synchronized (Singleton.class) {//第二次判断,抢到锁之后再次判断是否为nullif(instance == null) {instance = new Singleton();}}}return instance;}
}

Test:

public class Client {public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton instance1 = Singleton.getInstance();System.out.println(instance == instance1);}
}
true

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作
要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。
添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题

/**
* 双重检查方式
*/
public class Singleton {//私有构造方法private Singleton() {}private static volatile Singleton instance;//对外提供静态方法获取该对象public static Singleton getInstance() {//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际if(instance == null) {synchronized (Singleton.class) {//抢到锁之后再次判断是否为空if(instance == null) {instance = new Singleton();}}}return instance;}
}

6) 懒汉式-方式4(静态内部类方式)

  • 静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被static 修饰,保证只被实例化一次,并且严格保证实例化顺序
  • 说明:
    第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
  • 小结:
    静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
/**
* 静态内部类方式
*/
public class Singleton {//私有构造方法private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}//对外提供静态方法获取该对象public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

7)枚举方式

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式

/**
* 枚举方式
*/
public enum Singleton {INSTANCE;
}

2.3 单例模式存在的问题

破坏单例模式:
使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化反射

1)序列化反序列化

public class Singleton implements Serializable {//私有构造方法private Singleton() {}//定义一个静态内部类private static class SingletonHolder {//在内部类中声明并初始化外部类的对象private static final Singleton INSTANCE = new Singleton();}//提供公共的访问方式public static Singleton getInstance() {return SingletonHolder.INSTANCE;}//当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回public Object readResolve() {return SingletonHolder.INSTANCE;}}

Test:

/**
@Description:*      测试使用序列化破坏单例模式**      文件可以任意选择放在指定路径下* */
public class Client {public static void main(String[] args) throws Exception {//writeObject2File();readObjectFromFile();readObjectFromFile();}//从文件读取数据(对象)public static void readObjectFromFile() throws Exception {//1,创建对象输入流对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Desktop\\a.txt"));//2,读取对象Singleton instance = (Singleton) ois.readObject();System.out.println(instance);//释放资源ois.close();}//向文件中写数据(对象)public static void writeObject2File() throws Exception {//1,获取Singleton对象Singleton instance = Singleton.getInstance();//2,创建对象输出流对象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));//3,写对象oos.writeObject(instance);//4,释放资源oos.close();}
}
false

2)反射

public class Singleton {//私有构造方法private Singleton() {}private static volatile Singleton instance;//对外提供静态方法获取该对象public static Singleton getInstance() {if(instance != null) {return instance;}synchronized (Singleton.class) {if(instance != null) {return instance;}instance = new Singleton();return instance;}}
}

Test:

public class Test {public static void main(String[] args) throws Exception {
//获取Singleton类的字节码对象Class clazz = Singleton.class;
//获取Singleton类的私有无参构造方法对象Constructor constructor = clazz.getDeclaredConstructor();
//取消访问检查constructor.setAccessible(true);
//创建Singleton类的对象s1Singleton s1 = (Singleton) constructor.newInstance();
//创建Singleton类的对象s2Singleton s2 = (Singleton) constructor.newInstance();
//判断通过反射创建的两个Singleton对象是否是同一个对象System.out.println(s1 == s2);}
}
false

2.4 问题的解决

序列化、反序列方式破坏单例模式的解决方法

1)在Singleton类中添加readResolve() 方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

public class Singleton implements Serializable {//私有构造方法private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}//对外提供静态方法获取该对象public static Singleton getInstance() {return SingletonHolder.INSTANCE;}/*** 下面是为了解决序列化反序列化破解单例模式*/private Object readResolve() {return SingletonHolder.INSTANCE;}
}

2)反射方式破解单例的解决方法
通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。

public class Singleton {//私有构造方法private Singleton() {/*反射破解单例模式需要添加的代码*/if(instance != null) {throw new RuntimeException();}}private static volatile Singleton instance;//对外提供静态方法获取该对象public static Singleton getInstance() {if(instance != null) {return instance;}synchronized (Singleton.class) {if(instance != null) {return instance;}instance = new Singleton();return instance;}}
}
//私有构造方法
private Singleton() {/*反射破解单例模式需要添加的代码*/if(instance != null) {throw new RuntimeException();}
}

2.5 Runtime类使用的单例设计模式。

JDK源码解析-Runtime类

1. 源代码查看单例模式

Runtime类使用的是恶汉式(静态属性)方式来实现单例模式的。

public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Javaapplication.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtimeobject.** @return the <code>Runtime</code> object associated with thecurrent* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}...
}

2. 使用Runtime类中的方法

public class RuntimeDemo {public static void main(String[] args) throws IOException {//获取Runtime类对象Runtime runtime = Runtime.getRuntime();//返回 Java 虚拟机中的内存总量。System.out.println(runtime.totalMemory());//返回 Java 虚拟机试图使用的最大内存量。System.out.println(runtime.maxMemory());//创建一个新的进程执行指定的字符串命令,返回进程对象Process process = runtime.exec("ipconfig");//获取命令执行后的结果,通过输入流获取InputStream inputStream = process.getInputStream();byte[] arr = new byte[1024 * 1024* 100];int b = inputStream.read(arr);System.out.println(new String(arr,0,b,"gbk"));}
}

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

相关文章

CSDN竞赛24期题解

总结 本次竞赛的主要考点在于模拟&#xff0c;而且需要考虑的情况还蛮多&#xff0c;平时复杂点的模拟很少做&#xff0c;这次AC也花了挺长时间&#xff0c;后面还是需要夯实一下基础啊。 题目列表 1.计数问题 题目描述 试计算在区间 1 到 n 的所有整数中&#xff0c;数字…

【Linux_】环境变量

【Linux_】环境变量 心有所向&#xff0c;日复一日&#xff0c;必有精进专栏&#xff1a;《Linux_》作者&#xff1a;沂沐沐目录 【Linux_】环境变量 什么是环境变量 常见变量 查看环境变量方法 环境变量相关的命令 通过系统调用获取或设置环境变量 环境变量通常是具有全…

LeetCode 1802. 有界数组中指定下标处的最大值(C++)

思路&#xff1a; 首先根据题目要求&#xff0c;相邻数字的差距不能大于1&#xff0c;所以数组中的元素分布一定是以最大元素位置为塔顶&#xff0c;向两边发散的金字塔状&#xff0c;最小值为1&#xff0c;这样的结构能保证数组元素和一定是最小的&#xff08;只有1是重复元素…

Allegro如何输出第三方网表操作指导

Allegro如何输出第三方网表操作指导 在做PCB设计的时候,会需要输第三方网表,Allegro支持快速输出第三方网表,如下图 具体操作如下 选择File选择Export

TVM: End-to-End Optimization Stack for Deep Learning论文阅读

摘要 很多目前最为流行的深度学习框架&#xff0c;如 TensorFlow、MXNet、Caffe 和 PyTorch&#xff0c;支持在有限类型的服务器级 GPU 设备上获得加速&#xff0c;这种支持依赖于高度特化、供应商特定的 GPU 库。然而&#xff0c;专用深度学习加速器的种类越来越多&#xff0…

「数组」简析

前言 前言&#xff1a;研究一个数据结构的时候&#xff0c;首先讲的是增删改查。 文章目录前言一、一维数组1. 简介1&#xff09;定义2&#xff09;优点3&#xff09;缺点4&#xff09;使用数组的4个步骤2. 操作1&#xff09;访问2&#xff09;数组下标为什么从0开始&#xff1…

可笑 在网页上复制点东西 还需要money?进来看~

前言 哈喽 大家好&#xff01; 我是木易巷&#xff0c;我回来啦&#xff01;&#xff01;&#xff01; 现在好多平台都变成了不开会员不能复制这样的情况。士可杀不可辱&#xff01;作为一个优秀的复制粘贴工程师&#xff0c;在网页上复制点东西&#xff0c;还需要我掏钱&#…

Streamlit如何展示3D模型?

Streamlit 是一个非常好的创建 web demo 的库&#xff0c;但是对于单目深度估计很难找到可以展示 3D 模型的东西。 正如我刚刚在 Jupyter Notebook 中使用 obj2html 库可视化 3D 模型所做的那样&#xff0c;我创建了一个演示&#xff1a;HuggingFacae Spaces Monocular Depth …