Java 设计模式——单例模式

news2024/11/15 4:51:46

目录

  • 1.结构
  • 2.实现
    • 2.1.饿汉式
      • 2.1.1.静态变量
      • 2.1.2.静态代码块
      • 2.1.3.枚举方式
    • 2.2.懒汉式
      • 2.2.1.synchronized 线程安全
      • 2.2.2.双重检查锁
      • 2.2.3.静态内部类方式
  • 3.破坏单例模式
    • 3.1.序列化反序列化
    • 3.2.反射
  • 4.问题解决
  • 5.JDK 源码解析——Runtime 类

1.结构

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

(2)单例模式的主要有单例类和访问类这两个角色:

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

2.实现

单例模式分类两种:饿汉式和懒汉式。

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

2.1.饿汉式

2.1.1.静态变量

package com.itheima.patterns.singleton.hungryman1;

//饿汉式
public class Singleton{
    
    //私有构造方法
    private Singleton(){}
    
    //静态变量创建类的对象
    private static Singleton instance = new Singleton();

    //对外提供静态方法获取该对象
    public static Singleton getInstance(){
        return instance;
    }
}

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

2.1.2.静态代码块

package com.itheima.patterns.singleton.hungryman2;

public class Singleton{
    
    //私有构造方法
    private Singleton(){}
    
    //在静态代码块中进行创建
    private static Singleton instance;
    static {
        instance = new Singleton();
    }
    
    //对外提供静态方法获取该对象
    public static Singleton getInstance(){
        return instance;
    }
}

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

2.1.3.枚举方式

package com.itheima.patterns.singleton.hungryman3;

public enum Singleton {
    INSTANCE;
}

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

package com.itheima.patterns.singleton.hungryman;

public class Client {
    
    public static void main(String[] args) {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        // instance1 和 instance2 为同一个对象,结果为 true
        System.out.println(instance1 == instance2);  
    }
}

2.2.懒汉式

2.2.1.synchronized 线程安全

package com.itheima.patterns.singleton.Lazyman;

public class Singleton{
    
    //私有构造方法
    private Singleton(){}
    
    //声明 Singleton 类型的变量 instance,并未进行赋值
    private static Singleton instance;
    
    //使用关键字 synchronized 的目的在于保证线程安全
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

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

2.2.2.双重检查锁

package com.itheima.patterns.singleton.lazyman2;

public class Singleton {
    
    //私有构造方法
    private Singleton(){}
    
    //声明 Singleton 类型的变量 instance,并未进行赋值
    private static volatile Singleton instance;
    
    public static Singleton getInstance(){
        //第一次检查,若 instance 不为 null,则不进入抢锁阶段,直接返回实际值即可
        if (instance == null) {
            synchronized(Singleton.class){
                //第二次检查,得到锁之后再次判断 instance 是否为空
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

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

2.2.3.静态内部类方式

package com.itheima.patterns.singleton.lazyman3;

public class Singleton {
    
    //私有构造方法
    private Singleton(){}
    
    //静态内部类
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    
    //对外提供静态方法获取该对象
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

说明:第一次加载 Singleton 类时不会去初始化 INSTANCE,只有第一次调用 getInstance() 时,虚拟机才加载 SingletonHolder,并初始化 INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。总之,静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

3.破坏单例模式

破坏单例模式演示(序列化反序列化和反射)

3.1.序列化反序列化

package com.itheima.patterns.singleton.problem1;

import java.io.Serializable;

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;
    }
}
package com.itheima.patterns.singleton.problem1;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


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("E:\\testData\\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("E:\\testData\\a.txt"));
        //3.写对象
        oos.writeObject(instance);
        //4.释放资源
        oos.close();
    }
}

在这里插入图片描述

3.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;
		}
	}
}
package com.itheima.patterns.singleton.problem2;

import java.lang.reflect.Constructor;

public class Client {
    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
    }
}

4.问题解决

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

package com.itheima.patterns.singleton.reslove1;

import java.io.Serializable;

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)反射方式破解单例的解决方法
当通过反射方式调用构造方法进行创建创建时,直接抛异常,不运行此种操作。

package com.itheima.patterns.singleton.reslove1;

import java.io.Serializable;

public class Singleton implements Serializable {
    
    private static boolean flag = false;
    
    //私有构造方法
    private Singleton() {
        synchronized (Singleton.class){
            //若 flag 的值为 true,说明不是第一次访问,直接抛一个异常
            if(flag){
                throw new RuntimeException("不能创建多个对象!");
            }
            flag = true;
        }
    }
    
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    //解决序列化反序列化破解单例模式
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

5.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() {}
	...
}

从上面源代码中可以看出 Runtime 类使用的是饿汉式(静态属性)方式来实现单例模式的。

(2)使用 Runtime 类

package com.itheima.patterns.singleton.runtimedemo;

import java.io.IOException;
import java.io.InputStream;

public class RunTimeDemo {
    
    public static void main(String[] args) throws IOException {
        //获取RunTime类对象
        Runtime runtime = Runtime.getRuntime();
    
        System.out.println("JVM 空闲内存 =" + runtime.freeMemory() / (1024*1024) + "M");
        System.out.println("JVM 总内存 =" + runtime.totalMemory() / (1024*1024) + "M");
        System.out.println("JVM 可用最大内存 =" + runtime.maxMemory() / (1024*1024) + "M");
        
        //调用 runtime 的方法exec,参数为一个命令
        Process process = runtime.exec("ipconfig");
        //调用 process 对象的获取输入流的方法
        InputStream is = process.getInputStream();
        byte[] arr = new byte[1024 * 1024 * 100];
        int length = is.read(arr);
        System.out.println(new String(arr,0,length, "GBK"));
    }
}

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

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

相关文章

03-MySQL-基础篇-SQL之DDL语句

SQL之DDL语句 前言DDL数据库操作表操作查询操作数据类型案例修改删除 前言 *本篇来学习下SQL中的DDL语句 DDL 全称Data Definition Language&#xff0c;数据定义语言&#xff0c;用来定义数据库对象(数据库&#xff0c;表&#xff0c;字段) 数据库操作 查询所有数据库 s…

数字与字符的对决:力扣“将所有数字用字符替换”的独特方法

本篇博客会讲解力扣“1844. 将所有数字用字符替换”的解题思路&#xff0c;这是题目链接。 本题的解题思路是&#xff1a;遍历字符串&#xff0c;按照题目描述修改字符。 有一个需要注意的点&#xff1a;循环的结束条件是什么呢&#xff1f;是s[i] ! \0’吗&#xff1f;不是的…

多线程(2):线程同步

线程同步是在多线程编程过程中对数据保护的一种机制&#xff0c;保护的数据是共享数据。共享数据就是多个线程共同访问的一块资源&#xff0c;也就是一块内存。假设有3个线程&#xff0c;其中A,B线程在同一个时间点往这块内存中写数据&#xff0c;于此同时C线程往这块内存中读数…

视频编码流程 YUV数据编码为H264数据

文章目录 1.视频编码流程2.实战demo3.相关编码知识点讲解1. 参数设置问题:2. 关于av_opt_set3. 关于码流设置 1.视频编码流程 2.实战demo #ifndef MAINBACK_C #define MAINBACK_C #endif // MAINBACK_C #include <stdint.h> #include <stdio.h> #include <stdl…

火车头采集器AI伪原创[php源码]

本文介绍php版本的火车头采集器AI伪原创&#xff0c;对于网站的原创内容&#xff0c;站长朋友们一定很头疼。作为一个草根站长&#xff0c;自己写原创文章太累了。当然&#xff0c;我并不是说你不能写。自己写原创文章是不现实的。时间是最大的问题。 也许有的站长朋友会问&…

自定义封装Mybatis的过程

手写持久层框架思路分析&#xff1a; 步骤1:加载数据库配置信息&#xff0c;包括数据库url&#xff0c;端口&#xff0c;数据库名字&#xff1b;加载所有sqlmapper文件。 步骤2: 创建javaBean&#xff0c;全局配置类&#xff0c;Configuration&#xff0c;映射配置类&#xff…

香橙派和树莓派基于官方外设开发

香橙派和树莓派基于官方外设开发 1.wiringPi外设SDK安装 方式一&#xff1a; git clone https://github.com/orangepi-xunlong/wiringOP //下载源码 cd wiringOP //进入文件夹 sudo ./build clean //清除编译信息 sudo ./build //编译方式二 通过windows浏览器打开https://…

全网最新网络安全学习路线

在各大平台搜的网安学习路线都太粗略了。。。。看不下去了&#xff01; 我把自己整理的系统学习路线&#xff0c;拿出来跟大家分享了&#xff01;点击查看详细路线图 建议的学习顺序&#xff1a; 一、网络安全学习普法&#xff08;心里有个数&#xff0c;要进去坐几年&#xf…

Java入门教程||Java 网络编程||Java 发送邮件

Java 网络编程 网络编程是指编写运行在多个设备&#xff08;计算机&#xff09;的程序&#xff0c;这些设备都通过网络连接起来。 java.net包中J2SE的API包含有类和接口&#xff0c;它们提供低层次的通信细节。你可以直接使用这些类和接口&#xff0c;来专注于解决问题&#…

基于simulink视频处理系统多核仿真(附源码)

一、前言 此示例演示如何使用 Simulink中的数据流执行域在多个内核上运行视频处理系统。 数据流执行域允许您在计算密集型系统的设计中使用多个内核。此示例演示数据流作为子系统的执行域如何提高模型的模拟性能。 二、视频中的对象计数 此示例演示如何使用基本形态运算符从…

Docker部署Doris超详细图文教程

Doris安装有非常多的方法&#xff0c;这里主要介绍Docker中使用dev容器用来学习和测试的方法&#xff0c;避免在其他教程中踩坑(生产环境不建议使用Docker安装) 这里介绍一个不踩坑的docker本地单机版&#xff0c;笔者安装环境为Windows下的Docker&#xff0c;若为Linux系统安装…

SwiftUI + Swift 设备振动

如何让设备振动呢 iPhone 6S 3D Touch&#xff0c;可以识别轻&#xff0c;中&#xff0c;重三种按压力度&#xff0c;配合恰到好处的振动有利于提升交互体验&#xff0c;但后面的新设备都不支持 3D Touch 了&#xff0c;改为了检测按压时间&#xff0c;按同一个图标&#xff0…

如何用canvans实现地图上的运动轨迹

1.先需要一个地图的图片 2.通过canvas绘制出运动轨迹 // 创建渐变function createGradient(context, p0, p1) {const gradient context.createLinearGradient(p0.x, p0.y, p1.x, p1.y);gradient.addColorStop(0, "rgba(255, 0, 255, 0)");gradient.addColorStop(1,…

颜色渐变的数据密集适用的堆叠图

一般情况会用柱状图去堆叠&#xff0c;但是如果数据量太大了&#xff0c;就可考虑这种方式堆叠。可以呈现时间和数量上不同层次数据的变化。 效果图&#xff1a; 比较详细的注释一下源码&#xff1a; import matplotlib as mpl import matplotlib.pyplot as plt import numpy …

第七章:L2JMobius学习 – 登录服务LoginServer讲解

在上一个章节中&#xff0c;我们学习了网络数据传输的封装network。那么&#xff0c;在本章的登录服务LoginServer的讲解中&#xff0c;我们就来使用一下这个封装好的功能。Network的封装需要我们继承很多的接口或类。我们首先查看一下登录服务LoginServer的文件结构&#xff0…

[Android 13]Binder系列--获取ServiceManager

获取ServiceManager hongxi.zhu 2023-7-1 以SurfaceFlinger为例&#xff0c;分析客户端进程如何获取ServiceManager代理服务对象 主要流程 SurfaceFlinger中获取SM服务 frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp // publish surface flingersp<…

mysql单表查询,排序,分组查询,运算符

CREATE TABLE emp (empno int(4) NOT NULL, --员工编号ename varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,--员工名字job varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,--员工工作mgr int(4) NULL DEFAULT NU…

随机产生50个100以内的不重复的整数,设计位图排序算法进行排序。

1.问题 随机产生50个100以内的不重复的整数&#xff0c;设计位图排序算法进行排序。 2.设计思路 阶段1&#xff1a; 初始化一个空集合    for i[0,n)    bit[i]0 阶段2&#xff1a; 读入数据i&#xff0c;并设置bit[i]1    for each i in the input file    bit[i]1…

Tomcat 应用服务 WEB服务

简述&#xff1a; 中间件产品介绍 目前来说IBM的WebSphere&#xff0c;Oracle的Weblogic占据了市场上Java语言Web站点的部分份额&#xff0c;该两种软件由于无与伦比的性能及可靠性等优势被广泛应用于大型互联网公司的Web场景中&#xff0c;但是其高昂的价格也使得中小型互联…

DL-FWI:数据(第二次培训作业)

代码&#xff1a; import scipy.io import matplotlib import numpy as np import matplotlib.pylab as plt matplotlib.use(TkAgg) from mpl_toolkits.axes_grid1 import make_axes_locatable import cv2font21 {family: Times New Roman,weight: normal,size: 21,}font18 …