Java设计模式之单例模式以及如何防止通过反射破坏单例模式

单例模式

单例模式使用场景

​ 什么是单例模式?保障一个类只能有一个对象(实例)的代码开发模式就叫单例模式

​ 什么时候使用? 工具类!(一种做法,所有的方法都是static,还有一种单例模式让工具类只有一个实例) 某类工厂(SqlSessionFactory)

实现方式

1. 饿汉

java 复制代码
/**
 * 饿汉模式(迫切加载)
 */
public class Singleton01 {

    //构造私有化
    private Singleton01(){

    }

    //2 创建一个private对象
    private static final Singleton01 INSTANCE = new Singleton01();//这个地方就体现饿汉,一来就创建对象给他

    //3 提供一个static方法来获取你的这个单实例对象
    public static Singleton01 newInstance(){
        return INSTANCE;
    }
}

测试代码

java 复制代码
public class MyTest {
    public static void main(String[] args) {
        Singleton01 singleton01 = Singleton01.newInstance();
        Singleton01 singleton02 = Singleton01.newInstance();
        System.out.println(singleton01.equals(singleton02));
    }
}

输出为true说明两个对象是相同的

2. 懒汉(懒汉太懒了,要用的时候才能创建。)

java 复制代码
/**
 * 懒汉模式(懒加载) 单线程
 */
public class Singleton02 {

    private Singleton02() {
    }

    //2 创建一个private对象
    private static Singleton02 INSTANCE;

    //3 提供一个static方法来获取你的这个单实例对象 只适合在单线程中
    public static Singleton02 newInstance() {
    	if(INSTANCE == null){
    		INSTANCE = new Singleton02()
    	}
    	return INSTANCE;
 	}
}

测试代码

java 复制代码
public class MyTest {
    public static void main(String[] args) {
        Singleton02 singleton03 = Singleton02.newInstance();
        Singleton02 singleton04 = Singleton04.newInstance();
        System.out.println(singleton03.equals(singleton04));
    }
}

输出为true 说明两个对象是相同的
懒汉模式 只要不调用newInstance()方法 INSTANCE就一直为空 只用调用了才会创建

测试代码(如果多线程 就不是单例了)

java 复制代码
public class MyTest {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> Singleton02.newInstance()).start();
        }
    }
}

运行结果

说明有不同的线程都调用到了构造方法
解决方法:加锁
1.方法上加锁

public static synchronized Singleton02 newInstance()
缺点 :锁住整个方法 里面还有业务逻辑 效率降低很多

2.synchronized代码块

java 复制代码
/**
 * 懒汉模式(懒加载)
 */
public class Singleton02 {

    private Singleton02() {
    }

    //2 创建一个private对象
    private static Singleton02 INSTANCE;

    //3 提供一个static方法来获取你的这个单实例对象
    //方案1:方法锁,里面还有业务逻辑
    //public static synchronized  Singleton02 newInstance(){
    public static Singleton02 newInstance() {
        //方案2:锁代码块
        //synchronized (Singleton02.class){ // 100个线程哪怕有一个已经创建了也要排队
        if (INSTANCE == null) {
            //多线程来了? T1 T2
            synchronized (Singleton02.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton02();
                }
            }
        }
        //}

        //此处有10W行代码....
        return INSTANCE;
    }

    //普通方法
    public String getConnection() {
        return "I am connection!";
    }
}

只会有一个线程调用到构造方法
写到这里是不是觉得单例模式已经可以了?

但是这个代码也不是绝对安全的,不绝对是单例 利用反射去创建类的对象可以将单例进行破坏 写个测试代码

java 复制代码
public class MyTest {
public static void main(String[] args) throws Exception{
        //正常获取
        Singleton02 instance = Singleton02.newInstance();
        //通过反射获取
        Class<? extends Singleton02> aClass = instance.getClass();
        //注意:构造方法是私有的
        Constructor<? extends Singleton02> declaredConstructor = aClass.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Singleton02 instance02 = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance02);
    }
}

生成了两个对象,说明已经通过反射将单例破坏掉了

修改代码

java 复制代码
/**
 * 懒汉模式(懒加载)
 */
public class Singleton02 {

    private Singleton02() {
        synchronized (Singleton02.class) {
            if (INSTANCE != null){
                throw new RuntimeException("防止反射破坏单例");
            }
        }
    }

    //2 创建一个private对象
    private static Singleton02 INSTANCE;

    //3 提供一个static方法来获取你的这个单实例对象
    //方案1:方法锁,里面还有业务逻辑
    //public static synchronized  Singleton02 newInstance(){
    public static Singleton02 newInstance() {
        //方案2:锁代码块
        //synchronized (Singleton02.class){ // 100个线程哪怕有一个已经创建了也要排队
        if (INSTANCE == null) {
            //多线程来了? T1 T2
            synchronized (Singleton02.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton02();
                }
            }
        }
        //}

        //此处有10W行代码....
        return INSTANCE;
    }

    //普通方法
    public String getConnection() {
        return "I am connection!";
    }
}

那到这一步反射就不能搞破坏了吗?

答案是可以的 为什么?
因为第一次创建的时候使用的是正常的方式肯定会调用构造方法
将第一个打印放到反射创建之前就会打印出第一个的对象 第二次通过反射再拿一个对象就不行 如果我两次都使用反射来获取对象

java 复制代码
public class MyTest {
public static void main(String[] args) throws Exception{
Class<? extends Singleton02> aClass = Singleton02.class;
        //注意:构造方法是私有的
        Constructor<? extends Singleton02> declaredConstructor = aClass.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Singleton02 instance01 = declaredConstructor.newInstance();
        Singleton02 instance02 = declaredConstructor.newInstance();
        System.out.println(instance01);
        System.out.println(instance02);
	}
}

ok 单例又被破坏

还有没有其他方法?

不管是反射还是正常都会调用构造方法 那就先搞一个字段flag来进行校验

java 复制代码
package org.jhy._01singleton;

/**
 * 懒汉模式(懒加载)
 */
public class Singleton02 {

    private static Boolean flag = false;

    private Singleton02() {
//        synchronized (Singleton02.class) {
//            if (INSTANCE != null){
//                throw new RuntimeException("防止反射破坏单例");
//            }
//        }
        if (flag == false) {
            flag = true;
        } else {
            throw new RuntimeException("防止反射破坏单例");
        }
    }

    //2 创建一个private对象
    private static Singleton02 INSTANCE;

    //3 提供一个static方法来获取你的这个单实例对象
    //方案1:方法锁,里面还有业务逻辑
    //public static synchronized  Singleton02 newInstance(){
    public static Singleton02 newInstance() {
        //方案2:锁代码块
        //synchronized (Singleton02.class){ // 100个线程哪怕有一个已经创建了也要排队
        if (INSTANCE == null) {
            //多线程来了? T1 T2
            synchronized (Singleton02.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton02();
                }
            }
        }
        //}

        //此处有10W行代码....
        return INSTANCE;
    }

    //普通方法
    public String getConnection() {
        return "I am connection!";
    }
}
java 复制代码
public class MyTest {
public static void main(String[] args) throws Exception{
//通过反射获取
        Class<? extends Singleton02> aClass = Singleton02.class;
        //注意:构造方法是私有的
        Constructor<? extends Singleton02> declaredConstructor = aClass.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Singleton02 instance01 = declaredConstructor.newInstance();
        //获取字段名
        Field flag = aClass.getDeclaredField("flag");
        flag.setAccessible(true);
        //获取之后复位 认为又是第一次
        flag.set(instance01,false);
        Singleton02 instance02 = declaredConstructor.newInstance();
        System.out.println(instance01);
        System.out.println(instance02);
	}
}

ok,单例又被破坏了

那么反射真的就无所不能了吗???

让我们来看看newInstance()这个方法的源码

不能通过反射创建枚举 的对象,所以用枚举就能防止反射破坏单例

java 复制代码
public enum Singleton03 {

    INSTANCE;

    public void testMethod(){
        System.out.println("执行了单例类的方法");
    }
}

// Test.java
class Test {
    public static void main(String[] args) {
        //演示如何使用枚举写法的单例类
        Singleton03.INSTANCE.testMethod();
        System.out.println(Singleton03.INSTANCE);

        Singleton03 instance01 = Singleton03.INSTANCE;
        Singleton03 instance02 = Singleton03.INSTANCE;
        System.out.println(instance01.equals(instance02));
    }
}

结果显然为true 而且枚举类里面就不能用反射的方法 枚举里面只有一个实例那就是单例

相关推荐
爱上语文27 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people30 分钟前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
qmx_071 小时前
HTB-Jerry(tomcat war文件、msfvenom)
java·web安全·网络安全·tomcat
为风而战2 小时前
IIS+Ngnix+Tomcat 部署网站 用IIS实现反向代理
java·tomcat
技术无疆3 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
架构文摘JGWZ6 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
拾光师7 小时前
spring获取当前request
java·后端·spring
aPurpleBerry7 小时前
neo4j安装启动教程+对应的jdk配置
java·neo4j
我是苏苏7 小时前
Web开发:ABP框架2——入门级别的增删改查Demo
java·开发语言
xujinwei_gingko8 小时前
Spring IOC容器Bean对象管理-Java Config方式
java·spring