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 而且枚举类里面就不能用反射的方法 枚举里面只有一个实例那就是单例

相关推荐
一直学习永不止步3 分钟前
LeetCode题练习与总结:赎金信--383
java·数据结构·算法·leetcode·字符串·哈希表·计数
尘浮生6 分钟前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
尚学教辅学习资料14 分钟前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
雷神乐乐30 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
小刘|34 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
逊嘘1 小时前
【Java语言】抽象类与接口
java·开发语言·jvm
金池尽干1 小时前
设计模式之——观察者模式
观察者模式·设计模式
morris1311 小时前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
也无晴也无风雨1 小时前
代码中的设计模式-策略模式
设计模式·bash·策略模式