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

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

前言


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


单例模式


     单例模式(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();这行的时候就会抛出异常,这样就防止了破坏。


相关文章
|
10月前
|
设计模式 前端开发 Java
意外发现,原来你不知道自己每天都在用门面模式
一般的电商平台都是整合众多的子系统聚合到一起形成一个大型的购物平台,一般情况下,有很多现成的功能都不是重新开发的,而是要去对接已有的各个子系统,这些子系统可能涉及积分系统、支付系统、物流系统的接口调用。如果所有的接口调用全部由前端发送网络请求去调用现有接口,一则会增加前端开发人员的难度,二则会增加一些网络请求,影响页面性能。此时就可以发挥门面模式的优势了。将所有现成的接口全部整合到一个类中,由后端提供统一的接口供前端调用,这样前端开发人员就不需要关心各接口的业务关系,只需要把精力集中在页面交互上。我们用代码来模拟一个积分兑换礼品的业务场景。
76 0
意外发现,原来你不知道自己每天都在用门面模式
|
1月前
|
监控 安全 算法
悬垂引用与临时对象在C++中的深入探讨: 风险、原因与预防策略
悬垂引用与临时对象在C++中的深入探讨: 风险、原因与预防策略
102 3
|
10月前
|
存储 安全 Java
这9个单例被破坏的事故现场,你遇到过几个?
我们看到的单例模式通用写法,一般就是饿汉式单例的标准写法。饿汉式单例写法在类加载的时候立即初始化,并且创建单例对象。它绝对线程安全,在线程还没出现之前就实例化了,不可能存在访问安全问题。饿汉式单例还有另外一种写法,代码如下。
37 0
|
10月前
|
Java 关系型数据库 MySQL
双亲委派机制,懂吧~ 那什么情况下需要破坏它,知道吗?
双亲委派机制,懂吧~ 那什么情况下需要破坏它,知道吗?
55 0
|
10月前
|
设计模式 安全 Java
设计模式之单例模式(创建、单例破坏、防止破坏)
设计模式之单例模式(创建、单例破坏、防止破坏)
73 0
你担心大家会滥用的全局变量,大家(包括你自己)一定会滥用
你担心大家会滥用的全局变量,大家(包括你自己)一定会滥用
|
10月前
|
设计模式 SQL Java
哪些情况下的单例对象可能会破坏?
有位小伙伴在评论区留言,希望我分享一些设计模式相关的面试题。设计模式本身是很抽象的,但是在很多面试中又经常被问到,很多小伙伴其实都能答得上,但是又不知道怎么样回答才能让面试官满意,往往越简单的知识越能够体现出核心竞争力。
45 1
|
前端开发 Java 容器
为什么破坏双亲委派机制?
为什么破坏双亲委派机制?
149 0
|
存储 JSON 监控
3类代码安全风险如何避免?
企业和开发者在解决开源依赖包漏洞问题的同时,还需要考虑如何更全面地保障自己的代码数据安全。那么有哪些代码安全问题值得我们关注呢?
2017 0
3类代码安全风险如何避免?

热门文章

最新文章