Java单例模式的创建,破坏和防破坏详解

 更新时间:2021年09月08日 08:59:52   作者:牛哄哄的柯南  
大家所熟知的单例模式只能创建唯一一个实例,今天我们介绍几种常见的单例模式,同时说一说如何破坏单例模式,同时又怎么来防破坏

前言

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

单例模式

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

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

相关文章

  • SpringMVC中Model与Session的区别说明

    SpringMVC中Model与Session的区别说明

    这篇文章主要介绍了SpringMVC中Model与Session的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • SpringBoot静态资源目录访问

    SpringBoot静态资源目录访问

    今天小编就为大家分享一篇关于SpringBoot静态资源目录访问,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • JavaWeb Servlet生命周期细枝末节处深究

    JavaWeb Servlet生命周期细枝末节处深究

    Servlet指在服务器端执行的一段Java代码,可以接收用户的请求和返回给用户响应结果,下面这篇文章主要给大家介绍了关于JavaWeb.servlet生命周期的相关资料,需要的朋友可以参考下
    2022-10-10
  • springboot统一接口返回数据的实现

    springboot统一接口返回数据的实现

    这篇文章主要介绍了springboot统一接口返回数据的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • JavaWeb开发实现备忘录

    JavaWeb开发实现备忘录

    这篇文章主要为大家详细介绍了JavaWeb开发实现备忘录,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • java学习笔记之DBUtils工具包详解

    java学习笔记之DBUtils工具包详解

    下面小编就为大家分享一篇java学习笔记之DBUtils工具包详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-01-01
  • 简单了解JAVA构造方法

    简单了解JAVA构造方法

    构造方法作用就是对类进行初始化。 如果你没有定议任何构造方法的形式,程式会为你取一个不带任何参数的构造函数,那么你产生类的对像时只能用不带参数的方法.下面小编和大家来一起学习一下吧
    2019-06-06
  • Java 高并发十: JDK8对并发的新支持详解

    Java 高并发十: JDK8对并发的新支持详解

    本文主要介绍Java 高并发JDK8的支持,这里整理了详细的资料及1. LongAdder 2. CompletableFuture 3. StampedLock的介绍,有兴趣的小伙伴可以参考下
    2016-09-09
  • Spring中ApplicationEventPublisher发布订阅模式的实现

    Spring中ApplicationEventPublisher发布订阅模式的实现

    本文主要介绍了Spring中ApplicationEventPublisher发布订阅模式的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 详解Java内存管理中的JVM垃圾回收

    详解Java内存管理中的JVM垃圾回收

    这篇文章给大家分享了关于Java内存管理中的JVM垃圾回收的相关知识点内容,有兴趣的朋友们可以学习参考下。
    2018-08-08

最新评论