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

单例模式

单例模式使用场景

​ 什么是单例模式?保障一个类只能有一个对象(实例)的代码开发模式就叫单例模式
​ 什么时候使用? 工具类!(一种做法,所有的方法都是static,还有一种单例模式让工具类只有一个实例) 某类工厂(SqlSessionFactory)

实现方式

1. 饿汉

/**
 * 饿汉模式(迫切加载)
 */
public class Singleton01 {

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

    }

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

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

测试代码

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. 懒汉(懒汉太懒了,要用的时候才能创建。)

/**
 * 懒汉模式(懒加载) 单线程
 */
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;
 	}
}

测试代码

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就一直为空 只用调用了才会创建

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

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代码块

/**
 * 懒汉模式(懒加载)
 */
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!";
    }
}

在这里插入图片描述

只会有一个线程调用到构造方法

写到这里是不是觉得单例模式已经可以了?
但是这个代码也不是绝对安全的,不绝对是单例 利用反射去创建类的对象可以将单例进行破坏 写个测试代码

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);
    }
}

在这里插入图片描述

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

修改代码

/**
 * 懒汉模式(懒加载)
 */
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!";
    }
}

在这里插入图片描述

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

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来进行校验

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!";
    }
}

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()这个方法的源码
在这里插入图片描述

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谦谦孑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值