Java单例模式及其破坏和优化

b站上看了狂神的视频讲的单例模式。

现在主要提炼以下几点:
1、单例模式有饿汉式和懒汉式 饿汉式占用内存较大。
2、多线程下单例失效,需双重检测锁模式 即DCL懒汉式
3、DCL懒汉式可能存在指令重排问题,需要加上volatile 保证原子性
4、用反射可以破坏上述volatile的DCL懒汉式
5、针对上述问题可以添加第三重检测。
6、如果创建对象不走LazyMan.getInstance(),都走反射创建,那么就会绕过第三重检测,即第三重检测失效。
7、添加标志位即可解决上述问题。
8、如果标志位被修改即标志位失效则问题依旧存在。
9、使用枚举类,枚举类自带单例并且反射不能破坏枚举,写在源码里的。

下面是实验代码,我尽量写的清楚些,大家可以去看视频理解。

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class LazyMan {

    //针对第三次实验出现的问题 ,做出的优化,添加标志位
    private static boolean key = false;


    private LazyMan() {
        //针对第三次实验出现的问题 ,做出的优化,添加标志位
        if(key == false) {
            key = true;
        }else {
            throw new RuntimeException("在用反射破坏单例模式!");
        }
        //针对第二次实验出现的问题,做出的优化 ,第三重检测。
        // if(lazyMan!=null) {
        //     throw new RuntimeException("在用反射破坏单例模式!");
        // }

        //第一次实验添加
        System.out.println(Thread.currentThread().getName()+"--ok!");
    }

    private volatile static LazyMan lazyMan;


    //1、双重检测锁模式的懒汉式单例  DCL懒汉式 防止多线程下的并发的多个线程 
    public static LazyMan getInstance() {
        if(lazyMan == null) {
            synchronized(LazyMan.class){
                if(lazyMan==null) {
                    lazyMan = new LazyMan();
                    /*
                     * new不是一个原子性操作,分为三步: 分配内存 、初始化对象、将对象指向内存空间  
                     * 可能存在指令重排的问题,即三步混乱重排
                     * 需要加上volatile 保证原子性 避免指令重排
                     */
                }
            }
        }
        return lazyMan;
    }

   
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
         //第二次实验:用反射破坏上述volatile的DCL懒汉式
        // LazyMan instance1 = LazyMan.getInstance();
        // Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得LazyMan的构造器,因为是无参构造,所以参数为null
        // declaredConstructor.setAccessible(true);//权限为true,则无视了构造器中的private,然后就可以通过反射来创建对象
        // LazyMan instance2 = declaredConstructor.newInstance();
        // System.out.println(instance1);
        // System.out.println(instance2);//按照单例来说 这两个对象应该是一样的,但是他们不是一样的。则得出 :反射可以破坏单例

        //第三次实验:针对第三重优化,如果创建对象不走LazyMan.getInstance(),都走反射创建,那么就会绕过第三重优化,即第三重优化失效。
        // LazyMan instance1 = declaredConstructor.newInstance();
        // LazyMan instance2 = declaredConstructor.newInstance();
        // System.out.println(instance1);
        // System.out.println(instance2);

        //第四次实验,针对标志位优化,我们获得标志位 修改标志位,来破坏,所以需要用枚举类来使用单例,枚举类自带单例并且反射不能破坏枚举,写在源码里的
        Field key = LazyMan.class.getDeclaredField("key");
        key.setAccessible(true);
        Constructor<LazyMan> declaredConstructor1 = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor1.setAccessible(true);
        LazyMan instance1 = declaredConstructor1.newInstance();

        key.set(instance1, false);
        LazyMan instance2 = declaredConstructor1.newInstance();


        System.out.println(instance1);
        System.out.println(instance2);
       
    }
    //第一次实验:多线程下的单例
    // public static void main(String[] args) {
    //     for (int i = 0; i < 10; i++) {
    //         new Thread(()->{
    //             LazyMan.getInstance();
    //         }).start();
    //     }
    // }


    
    
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值