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

相关推荐
drebander18 分钟前
使用 Java Stream 优雅实现List 转化为Map<key,Map<key,value>>
java·python·list
乌啼霜满天24922 分钟前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc
tangliang_cn27 分钟前
java入门 自定义springboot starter
java·开发语言·spring boot
程序猿阿伟28 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
Grey_fantasy38 分钟前
高级编程之结构化代码
java·spring boot·spring cloud
弗锐土豆44 分钟前
工业生产安全-安全帽第二篇-用java语言看看opencv实现的目标检测使用过程
java·opencv·安全·检测·面部
Elaine2023911 小时前
零碎04 MybatisPlus自定义模版生成代码
java·spring·mybatis
小小大侠客1 小时前
IText创建加盖公章的pdf文件并生成压缩文件
java·pdf·itext
一二小选手1 小时前
【MyBatis】全局配置文件—mybatis.xml 创建xml模板
xml·java·mybatis
猿java1 小时前
Linux Shell和Shell脚本详解!
java·linux·shell