单例模式的创建、破坏和防破坏

简介: 单例模式的创建、破坏和防破坏

前言


大家所熟知的单例模式只能创建唯一一个实例,今天我们介绍几种常见的单例模式,同时说一说如何破坏单例模式,同时又怎么来防破坏。


单例模式


     单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

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


1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。


单例模式的几种实现方式


懒汉式,线程不安全


下面的懒汉式是线程不安全的,支持懒加载,因为没有加锁 synchronized,所以严格意义上它并不算单例模式。


样例代码:


public class Singleton{
  private static Singleton instance;
  private Singleton(){
  }
  public static Singleton getInstance(){
      if(instance == null){
          return new Singleton();
      }
      return instance;
  }
}

懒汉式,线程安全


下面的这种方式可以保证线程安全,支持懒加载,优点是第一次调用才初始化,避免内存浪费。缺点是必须加锁synchronized 才能保证单例,但加锁会影响效率。


样例代码:


public class Singleton{
  private static Singleton instance;
  private Singleton(){
  }
  public static synchronized Singleton getInstance(){
      if(instance == null){
          return new Singleton();
      }
      return instance;
  }
}

饿汉式


饿汉式,比较常用,但是容易参生垃圾对象,这种方式不支持懒加载,线程安全,优点是没有加锁,执行效率会提高。缺点是类加载时就初始化,浪费内存。


样例代码:


public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){
    }  
    public static Singleton getInstance() {  
      return instance;  
    }  
}

双检锁/双重校验锁


这种方式支持懒加载,线程安全,这种方式采用双锁机制,安全且在多线程情况下能保持高性能。


样例代码:


public class Singleton {  
    private volatile static Singleton instance;
    private Singleton(){
    }
    public static Singleton getInstance(){
        if(instance==null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    } 
}

登记式/静态内部类


这种方式支持懒加载,线程安全,这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。


public class Singleton {  
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private  Singleton(){
    }
    public static final Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }  
}

枚举


这种实现方式不支持懒加载,线程安全,不过还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。


public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

模拟一个数据库连接类:


public enum SingletonEnum {
    INSTANCE;
    private DBConnection connection = null;
    SingletonEnum(){
        connection = new DBConnection();
    }
    public DBConnection getConnection(){
        return connection;
    }
}
public class DBConnection{
}
public class TestConnection {
    public static void main(String[] args) {
        DBConnection con1 = DataSourceEnum.DATASOURCE.getConnection();
        DBConnection con2 = DataSourceEnum.DATASOURCE.getConnection();
        System.out.println(con1 == con2); //输出结果为true。
    }
}

破坏单例模式


破坏单例模式主要有两种方法:反射、反序列化


我们就拿最经典的饿汉式来演示破坏和防破坏。


未破坏的情况


Singleton:

/**
 * Keafmd
 *
 * @ClassName: Singleton
 * @Description: 单例模式
 * @author: 牛哄哄的柯南
 * @date: 2021-09-07 10:53
 */
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){
    }
    public static Singleton getInstance() {
        return instance;
    }
}

测试类(未破坏):


/**
 * Keafmd
 *
 * @ClassName: SigletonTest
 * @Description: 测试类
 * @author: 牛哄哄的柯南
 * @date: 2021-09-07 11:04
 */
public class SingletonTest {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance(); 
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1); //com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
        System.out.println(instance2); //com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
        System.out.println(instance1==instance2); //true
    }
}

破坏后的情况


Singleton:(不改变)


/**
 * Keafmd
 *
 * @ClassName: Singleton
 * @Description: 单例模式
 * @author: 牛哄哄的柯南
 * @date: 2021-09-07 10:53
 */
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){
    }
    public static Singleton getInstance() {
        return instance;
    }
}

测试类(通过反射破坏):


package com.keafmd.Study.designPatterns.Blog;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
 * Keafmd
 *
 * @ClassName: SigletonTest
 * @Description: 测试类
 * @author: 牛哄哄的柯南
 * @date: 2021-09-07 11:04
 */
public class SingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1); //com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
        System.out.println(instance2); //com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
        System.out.println(instance1==instance2); //true
        //=====================破坏单例模式===================
        //通过反射获取实例,破坏单例
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton instance11 = constructor.newInstance();
        Singleton instance22 = constructor.newInstance();
        System.out.println(instance11); //com.keafmd.Study.designPatterns.Blog.Singleton@511d50c0
        System.out.println(instance22); //com.keafmd.Study.designPatterns.Blog.Singleton@60e53b93
        System.out.println(instance11==instance22); //false 证明单例模式已经被破坏
    }
}

输出结果:


com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
true
com.keafmd.Study.designPatterns.Blog.Singleton@511d50c0
com.keafmd.Study.designPatterns.Blog.Singleton@60e53b93
false
Process finished with exit code 0

这种破坏是通过java的反射机制,创建一个实例,这种破坏方法通过setAccessible(true)的方法是java跳过检测语法,可以临时改变访问权限,就可以获取私有成员变量。


单例模式的防破坏


其实防止破坏最简单的一种方式就是判断下有没有创建过实例,如果是第二次创建实例对象的时候,直接抛出异常,阻止创建即可。


重写Singleton类:


package com.keafmd.Study.designPatterns.Blog;
/**
 * Keafmd
 *
 * @ClassName: Singleton
 * @Description: 单例模式
 * @author: 牛哄哄的柯南
 * @date: 2021-09-07 10:53
 */
public class Singleton {
    //阻止实例化
    private static boolean flag=true;
    private static Singleton instance = new Singleton();
    private Singleton (){
        if(!flag){
            throw new RuntimeException("这个单例模式类不能创建更多的对象了");
        }
    }
    public static Singleton getInstance() {
        if(flag){
            flag=false; //第一次创建时就会改变flag的值,导致后面创建不成功
        }
        return instance;
    }
}

测试类(未改变):


package com.keafmd.Study.designPatterns.Blog;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
 * Keafmd
 *
 * @ClassName: SigletonTest
 * @Description: 测试类
 * @author: 牛哄哄的柯南
 * @date: 2021-09-07 11:04
 */
public class SingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance1==instance2);
        //=====================破坏单例模式===================
        //通过反射获取实例,破坏单例
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton instance11 = constructor.newInstance();
        Singleton instance22 = constructor.newInstance();
        System.out.println(instance11);
        System.out.println(instance22);
        System.out.println(instance11==instance22);
    }
}

输出结果:


com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
com.keafmd.Study.designPatterns.Blog.Singleton@610455d6
true
Exception in thread "main" java.lang.reflect.InvocationTargetException
  at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  at com.keafmd.Study.designPatterns.Blog.SingletonTest.main(SingletonTest.java:28)
Caused by: java.lang.RuntimeException: 这个单例模式类不能创建更多的对象了
  at com.keafmd.Study.designPatterns.Blog.Singleton.<init>(Singleton.java:28)
  ... 5 more
Process finished with exit code 1

这样在执行到Singleton instance22 = constructor.newInstance();这行的时候就会抛出异常,这样就防止了破坏。


相关文章
|
数据可视化 PyTorch 算法框架/工具
零一万物Yi-34B-Chat 微调模型及量化版开源!魔搭社区最佳实践教程!
11月24日,零一万物基正式发布并开源微调模型 Yi-34B-Chat,可申请免费商用。同时,零一万物还为开发者提供了 4bit/8bit 量化版模型,Yi-34B-Chat 4bit 量化版模型可以直接在消费级显卡(如RTX3090)上使用。魔搭社区已支持下载、推理训练体验,并推出相关教程,欢迎大家来玩!
|
29天前
|
Kubernetes 关系型数据库 MySQL
【赵渝强老师】使用Helm简化Kubernetes(K8s)应用的部署和管理
Helm是Kubernetes的应用包管理工具,可简化应用部署与管理。通过Chart模板定义应用配置,支持快速安装、升级和卸载。本文介绍Helm核心概念、部署方法,并实战演示部署MySQL和创建自定义Nginx Chart。
190 3
|
缓存 前端开发 Java
nacos常见问题之开启鉴权后客户端报403升级版本如何解决
Nacos是阿里云开源的服务发现和配置管理平台,用于构建动态微服务应用架构;本汇总针对Nacos在实际应用中用户常遇到的问题进行了归纳和解答,旨在帮助开发者和运维人员高效解决使用Nacos时的各类疑难杂症。
2692 0
|
SQL 关系型数据库 MySQL
Mac下安装hive
Mac下安装hive
943 0
|
JSON JavaScript 数据可视化
SpringBoot+Thymeleaf+ECharts实现大数据可视化(基础篇)
SpringBoot+Thymeleaf+ECharts实现大数据可视化(基础篇)
1535 0
SpringBoot+Thymeleaf+ECharts实现大数据可视化(基础篇)
|
3月前
|
前端开发 安全 Java
基于springboot+vue开发的会议预约管理系统
一个完整的会议预约管理系统,包含前端用户界面、管理后台和后端API服务。 ### 后端 - **框架**: Spring Boot 2.7.18 - **数据库**: MySQL 5.6+ - **ORM**: MyBatis Plus 3.5.3.1 - **安全**: Spring Security + JWT - **Java版本**: Java 11 ### 前端 - **框架**: Vue 3.3.4 - **UI组件**: Element Plus 2.3.8 - **构建工具**: Vite 4.4.5 - **状态管理**: Pinia 2.1.6 - **HTTP客户端
379 4
基于springboot+vue开发的会议预约管理系统
|
测试技术 网络架构 C++
使用MergeKit创建自己的专家混合模型:将多个模型组合成单个MoE
MoE架构通过MergeKit实现新突破,允许整合预训练模型创建frankenMoEs,如FrankenMoE,区别于头开始训练的MoEs。MergeKit工具支持选择专家模型,定义正负提示,并生成MoE配置。
586 2
|
存储 监控 调度
云迁移中心CMH:助力企业高效上云实践全解析
随着云计算的发展,企业上云已成为创新发展的关键。然而,企业上云面临诸多挑战,如复杂的应用依赖梳理、成本效益分析等。阿里云推出的云迁移中心(CMH)旨在解决这些问题,提供自动化的系统调研、规划、迁移和割接等功能,简化上云过程。CMH通过评估、准备、迁移和割接四个阶段,帮助企业高效完成数字化转型。未来,CMH将继续提升智能化水平,支持更多行业和复杂环境,助力企业轻松上云。
|
算法 Java 调度
深入理解 Thread 类的 Yield 方法
【8月更文挑战第22天】
708 4
|
SQL Java 数据库连接
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践