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

目录
  • 前言
  • 单例模式
  • 单例模式的几种实现方式
    • 懒汉式,线程不安全
    • 懒汉式,线程安全
    • 饿汉式
    • 双检锁/双重校验锁
    • 登记式/静态内部类
    • 枚举
  • 破坏单例模式
    • 未破坏的情况
    • 破坏后的情况
  • 单例模式的防破坏
  • 总结

前言

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

单例模式

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

总结

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

(0)

相关推荐

  • java单例模式实现的方法

    1.最基本的单例模式 /** * @author LearnAndGet * @time 2018年11月13日 * 最基本的单例模式 */public class SingletonV1 { private static SingletonV1 instance = new SingletonV1();; //构造函数私有化 private SingletonV1() {} public static SingletonV1 getInstance() { return instance; }

  • JAVA设计模式---单例模式你知道吗

    目录 单例模式的介绍 单例模式实现的八种方式 饿汉式 静态常量 静态代码块 懒汉式 线程不安全的写法 线程安全,同步锁-效率低,不推荐 线程安全,同步代码块-无法解决线程安全问题,不推荐 双重检查-解决线程安全和懒加载问题–推荐使用 静态内部类-可以实现懒加载,线程安全,推荐使用 枚举 单例模式注意事项 总结 单例模式的介绍 单例模式实现的八种方式 饿汉式 静态常量 步骤: 1.构造器私有化(防止new) 2.类的内部创建对象 3.向外暴露一个静态的公共方法-getInstance //饿汉式静

  • Java开发中为什么要使用单例模式详解

    一.什么是单例模式? 单例设计模式(Singleton Design Pattern)理解起来非常简单.一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式. 二.实战案例一:处理资源访问冲突 我们先来看第一个例子.在这个例子中,我们自定义实现了一个往文件中打印日志的 Logger 类.具体的代码实现如下所示: public class Logger { private FileWriter writer; public Logger() {

  • Java设计模式之单例模式简介

    一.饿汉式(静态常量) public class Face { private static final Face instance = new Face(); static Face getInstance() { return instance; } private Face() { } } 优点:这种写法比较简单,就是在类装载的时候就完成实例化.避免了线程同步问题. 缺点:在类装载的时候就完成实例化,没有懒加载的效果.如果从始至终从未使用过这个实例,则会造成内存的浪费. 二.饿汉式(静态代

  • JAVA破坏单例模式的方式以及避免方法

    单例模式,大家恐怕再熟悉不过了,其作用与实现方式有多种,这里就不啰嗦了.但是,咱们在使用这些方式实现单例模式时,程序中就真的会只有一个实例吗? 聪明的你看到这样的问话,一定猜到了答案是NO.这里笔者就不卖关子了,开门见山吧!实际上,在有些场景下,如果程序处理不当,会无情地破坏掉单例模式,导致程序中出现多个实例对象. 下面笔者介绍笔者已知的三种破坏单例模式的方式以及避免方法. 1.反射对单例模式的破坏 我们先通过一个例子,来直观感受一下 (1)案例 DCL实现的单例模式: public class

  • java 单例模式容易忽略的细节

    java单例模式 直接讲实现单例模式的两种方法:懒汉式和饿汉式,单例模式的概念自己上网搜吧这里就不讲了! 这里会涉及到java中的jvm,如果你没有这方面的知识,我建议你先去补补,不然会有点迷糊! 首先说说类什么时候进行加载? java虚拟机没有进行强制性的约束,但是对于初始化却严格规定了有且只有4种情况必须先对类进行初始化. 我们要知道的是在类加载的过程中,加载.验证.准备是在初始化之前完成的,所以进行了初始化,加载.验证.准备自然就在之前完成了. 然后这四种情况是分别遇到 new . get

  • Java单例模式的8种写法(推荐)

    单例:Singleton,是指仅仅被实例化一次的类. 饿汉单例设计模式 一.饿汉设计模式 public class SingletonHungry { private final static SingletonHungry INSTANCE = new SingletonHungry(); private SingletonHungry() { } public static SingletonHungry getInstance() { return INSTANCE; } } 因为单例对象

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

    目录 前言 单例模式 单例模式的几种实现方式 懒汉式,线程不安全 懒汉式,线程安全 饿汉式 双检锁/双重校验锁 登记式/静态内部类 枚举 破坏单例模式 未破坏的情况 破坏后的情况 单例模式的防破坏 总结 前言 大家所熟知的单例模式只能创建唯一一个实例,今天我们介绍几种常见的单例模式,同时说一说如何破坏单例模式,同时又怎么来防破坏. 单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种

  • Java零基础也看得懂的单例模式与final及抽象类和接口详解

    目录 1.单例模式 1.饿汉式和懒汉式的实现 2.饿汉式 3.懒汉式 4.懒汉式和饿汉式的区别 2.final的使用 1.基本介绍 2.使用场景 3.使用注意事项和细节讨论 3.抽象类 1.基本介绍 2.使用注意事项和细节讨论 4.接口 1.基本介绍 2.注意事项和细节讨论 3.实现接口与继承的区别 4.接口与继承同时出现如何访问属性 5.接口的多态特性 1.单例模式 1.饿汉式和懒汉式的实现 步骤: 将构造器私有化 类的内部创建对象 向外暴露一个静态的公共方法 2.饿汉式 class Scho

  • JAVA 枚举单例模式及源码分析的实例详解

    JAVA 枚举单例模式及源码分析的实例详解 单例模式的实现有很多种,网上也分析了如今实现单利模式最好用枚举,好处不外乎三点: 1.线程安全 2.不会因为序列化而产生新实例 3.防止反射攻击但是貌似没有一篇文章解释ENUM单例如何实现了上述三点,请高手解释一下这三点: 关于第一点线程安全,从反编译后的类源码中可以看出也是通过类加载机制保证的,应该是这样吧(解决) 关于第二点序列化问题,有一篇文章说枚举类自己实现了readResolve()方法,所以抗序列化,这个方法是当前类自己实现的(解决) 关于

  • java 中序列化与readResolve()方法的实例详解

    java 中序列化与readResolve()方法的实例详解 readResolve方法是作用是什么?这个方法跟对象的序列化相关(这样倒是解释了为什么 readResolve方法是private修饰的). 怎么跟对象的序列化相关了? 下面我们先简要地回顾下对象的序列化.一般来说,一个类实现了 Serializable接口,我们就可以把它往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象.不过当序列化遇到单例时,里边就有了个问题:从内存读出而组装的对象破坏了单例的规则.单例是要

  • Java使用备忘录模式实现过关类游戏功能详解

    本文实例讲述了Java使用备忘录模式实现过关类游戏功能.分享给大家供大家参考,具体如下: 一.模式定义 备忘录模式,在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象外部保存这个状态.这样以后就可将该对象恢复到原先保存的状态. 二.模式举例 1模式分析 我们借用过关类游戏来说明这一模式. 2备忘录模式静态类图 3代码示例(黑箱备忘录模式) 3.1创建备忘录窄接口一INarrowMemento package com.demo.memento; /** * 备忘录窄接口(不提供任何方法,外部

  • java 最新Xss攻击与防护(全方位360°详解)

    前沿 XSS防范属于前端还是后端的责任 ? XSS 防范是后端 RD(研发人员)的责任,后端 RD 应该在所有用户提交数据的接口,对敏感字符进行转义,才能进行下一步操作. 所有要插入到页面上的数据,都要通过一个敏感字符过滤函数的转义,过滤掉通用的敏感字符后,就可以插入到页面中. 公司的搜索页面如果你是下面的写法.那么他可能存在Xss注入 <input type="text" value="<%= getParameter("keyword")

  • Java设计模式七大原则之合成复用原则详解

    目录 定义 案例 需求 方案一 方案二 方案三 对比分析 总结 设计原则的核心思想 定义 合成复用原则(Composite Reuse Principle),即尽量使用组合/聚合的方式,而不是使用继承. 案例 需求 现在假设有一个A类,里面有两个方法,有一个B类,想要复用这两个方法,请问有几种方案 方案一 继承的方式 定义A类,并定义两个方法 /** * 类A * @author:liyajie * @createTime:2022/2/9 9:50 * @version:1.0 */ publ

  • FasfDFS整合Java实现文件上传下载功能实例详解

    在上篇文章给大家介绍了FastDFS安装和配置整合Nginx-1.13.3的方法,大家可以点击查看下. 今天使用Java代码实现文件的上传和下载.对此作者提供了Java API支持,下载fastdfs-client-java将源码添加到项目中.或者在Maven项目pom.xml文件中添加依赖 <dependency> <groupId>org.csource</groupId> <artifactId>fastdfs-client-java</arti

  • java 中Excel转shape file的实例详解

    java  中Excel转shape file的实例详解 概述: 本文讲述如何结合geotools和POI实现Excel到shp的转换,再结合前文shp到geojson数据的转换,即可实现用户上传excel数据并在web端的展示功能. 截图: 原始Excel文件 运行耗时 运行结果 代码: package com.lzugis.geotools; import com.lzugis.CommonMethod; import com.vividsolutions.jts.geom.Coordina

  • java通过ssh连接服务器执行shell命令详解及实例

    java通过ssh连接服务器执行shell命令详解 java通过ssh连接服务器执行shell命令:JSch 是SSH2的一个纯Java实现.它允许你连接到一个sshd 服务器,使用端口转发,X11转发,文件传输等等.你可以将它的功能集成到你自己的 程序中.同时该项目也提供一个J2ME版本用来在手机上直连SSHD服务器. SSH是Secure Shell的缩写,一种建立在应用层和传输层基础上的安全协议.SSH在连接和传送过程中会加密所有数据,可以用来在不同系统或者服务器之间进行安全连接.SSH提

随机推荐