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

news2024/12/26 0:04:11

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类型的变量instance
    private static Singleton instance; //只是声明一个该类型的变量,并没有进行赋值

    //对外提供访问方式
    public static synchronized 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) {
                //第二次判断,抢到锁之后再次判断是否为null
                if(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类的对象s1
        Singleton s1 = (Singleton) constructor.newInstance();
//创建Singleton类的对象s2
        Singleton 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 Java
	application.
	* Most of the methods of class <code>Runtime</code> are instance
	* methods and must be invoked with respect to the current runtime
	object.
	*
	* @return the <code>Runtime</code> object associated with the
	current
	* 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.coloradmin.cn/o/173418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows 下 VS Code 远程连接 Ubuntu 并配置免密登录

文章目录1.安装 Visual Studio Code2.安装必要的插件3.为本机生成 SSH 密钥对4.将公钥拷贝到 Ubuntu 上5.配置 Remote 插件6.关闭远程连接7.卸载 Visual Studio Code7.1 在控制面板中找到 Visual Studio Code 将其卸载7.2 删除之前安装过的插件7.3 删除用户信息和缓存信息1.安装…

BootStrap使用笔记+案例

前端开发 第三节BootStrap BootStrap BootStrap是别人写好的CSS样式&#xff0c;如何使用BootStrap&#xff1a; 下载BootStrap使用 在页面上引入BootStrap编写HTML时&#xff0c;按照BootStrap的规定来编写 自定制 开发版本&#xff1a;bootstrap.css 生产版本&#xf…

【HTML | CSS】春节将至,为网页挂上精美的灯笼吧(附源码)程序员的浪漫

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

在线支付系列【2】支付宝和微信支付发展史

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 文章目录支付宝发展史起步阶段发展阶段上升阶段微信支付发展史支付宝 支付宝是阿里巴巴集团于 2004 年推出的一款第三方支付的产品&#xff0c;目前隶属于蚂蚁金服&#xff08;杭州&#xff09;网络技术…

2023年最推荐苹果、华为、荣耀:一场“以人为中心”的口碑竞技

消费者需求是市场发展的风向标。经济学界早有洞察&#xff0c;诺贝尔经济学奖得主哈耶克曾提出“消费者主权”理论&#xff0c;认为生产什么&#xff0c;生产多少&#xff0c;应当由消费者的意愿和偏好决定。手机市场也是如此&#xff0c;存量竞争下&#xff0c;消费者进一步掌…

软件测试复习09:集成测试、系统测试、验收测试、回归测试

作者&#xff1a;非妃是公主 专栏&#xff1a;《软件测试》 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录集成测试集成测试方法自顶向下自底向上系统测试验收测试回归测试集成测试 把模块拼装到一起&#…

【电力系统经济调度】多元宇宙算法求解电力系统多目标优化问题(Matlab实现)【电气期刊论文复现】

目录 0 概述 1 环境经济调度数学模型 2 多元宇宙算法 3 运行结果 4 Matlab代码实现 目录 0 概述 1 环境经济调度数学模型 2 多元宇宙算法 3 Matlab代码实现 3.1 主函数&#xff1a; 3.2 目标函数 4 运行结果 0 概述 多元宇宙算法求解电力系统多目标优化算法有…

【GD32F427开发板试用】CAN总线了解和回环测试

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;HonestQiao CAN总线是个好东西&#xff0c;据说用了的都说好。只要是09年之后的车都有CAN总线&#xff0c;要是摸得透的话&#xff0c;你还能通…

微信小程序开发尚学堂 介绍 项目结构 组件

一、微信小程序介绍1. 微信小程序介绍微信小程序&#xff0c;简称小程序&#xff0c;是一种不需要下载安装即可使用的应用&#xff0c;它实现了应用”触手可及”的梦想&#xff0c;用户扫一扫或搜一下即可打开应用。说明&#xff1a;小程序是需要下载的&#xff0c;小程序的占用…

用于多核DSP开发的核间通信

TI的多核DSP以TMS320C6678为例&#xff0c;它是多核同构的处理器&#xff0c;内部是8个相同的C66x CorePac。对其中任意一个核的开发就和单核DSP开发的流程是类似的。   但是如果仅仅只是每个核独立开发&#xff0c;那么很难体现出多核的优势。要充分发挥多核处理器的性能&am…

Docker 应用实践-容器篇

在 Docker 镜像篇中&#xff0c;我们了解到 Docker 镜像类似于模板&#xff0c;那么 Docker 容器就相当于从模板复制过来运行时的实例&#xff0c;Docker 容器可以被创建、复制、暂停和删除等。 每一个 Docker 容器在运行时都是以镜像为基础层&#xff0c;并在镜像的基础上创建…

PWM 应用

1.PWM 同样也是通过 sysfs 方式进行操控&#xff0c;进入到/sys/class/pwm 目录下。8 个以 pwmchipX&#xff08;X 表示数字 0~7&#xff09;命名的文件夹&#xff0c;这八个文件夹其实就对应了 I.MX6U的 8 个 PWM 控制器&#xff0c; I.MX6U 总共有 8 个 PWM 控制器。2.进入到…

云GPU服务器部署及pycharm远程连接

我们在之前使用CoLab来运行项目&#xff0c;但其存在时长限制问题所以并不是很理想&#xff0c;今天博主发现腾讯云服务目前在搞活动&#xff0c;比较实惠&#xff0c;便买了一台来体验一下。 直接搜索gpu服务器租用即可找到 购买服务器 博主买的是NVIDIA T4 GPU,还是较有性…

web流程设计器andflow_js已支持自定义颜色

andflow_js 是一个web 开源流程设计框架&#xff0c;目前版本已支持对各类节点单独设置颜色。除了颜色之外&#xff0c;andflow_js还支持通过setActionInfo、setGroupInfo、setListInfo、setTipInfo 等设置节点各种参数。 设置节点的颜色&#xff1a; 设置节点边框颜色 and…

Arduino环境下对NodeMCU ESP8266的闪存flash系统使用

flash存储简答介绍 参考&#xff1a;https://www.elecfans.com/consume/572040.html flash存储器又称闪存&#xff08;快闪存储器&#xff09;&#xff0c;就其本质而言&#xff0c;flash存储器属于EEPROM&#xff08;电擦除可编程只读存储器&#xff09;类型。是一种长寿命的…

矩阵求导学习

布局 分子布局 ∂y∂x(∂y∂x1∂y∂x2⋯∂y∂xn)\frac{\partial y}{\partial \mathbf{x}} \begin{pmatrix} \frac{\partial y}{\partial x_1} & \frac{\partial y}{\partial x_2} &\cdots & \frac{\partial y}{\partial x_n} \end{pmatrix} ∂x∂y​(∂x1​∂y​​…

tkinter绘制组件(38)——状态开关按钮

tkinter绘制组件&#xff08;38&#xff09;——状态开关按钮引言布局函数结构按钮主体渐变色处理颜色处理基础渐变色列表形成列表样式绑定完整函数代码效果测试代码最终效果github项目pip下载结语引言 TinUI里的状态开关按钮&#xff08;togglebutton&#xff09;和开关&…

DPU网络开发SDK—DPDK(五)

rte_eal_init 接上次内容继续对rte_eal_init()所做的工作进行分析。 18. 检查是否允许直接物理地址访问 rte_eal_using_phys_addrs()会去检查当前系统是否允许在进程虚拟地址空间直接访问物理地址。需要有两个支持条件&#xff1a;存在大页内存和能够进行虚拟地址到物理地址…

智云通CRM:如何把握拓客成交的三种时间?

在我们邀约、拜访及展示产品之后&#xff0c;客户就一定会成为你的客户吗&#xff1f;当然不尽然&#xff0c;这取决于众多因素。任何事情都不是一蹴而就的&#xff0c;我们不能刚到某个场合认识了一个人&#xff0c;就一定要立即成交&#xff0c;或者反过来因为对方此时没有需…

Pytorch实战笔记(2)——CNN实现情感分析

本文展示的是使用 Pytorch 构建一个 TextCNN 来实现情感分析。本文的架构是第一章详细介绍 TextCNN&#xff08;不带公式版&#xff09;&#xff0c;第二章是核心代码部分。 目录1. TextCNN2. TextCNN 实现情感分析参考1. TextCNN 相较于 LSTM 而言&#xff0c;我个人其实是没…