设计模式(1)---单例模式

饿汉式单例

饿汉式单例设计模式,就是将类的静态实例作为该类的一个成员变量,也就是说在 JVM 加载它的时候就已经创建了该类的实例,因此它不会存在多线程的安全问题

package com.study.single;

//饿汉式单例
public class Hungry {
    //造成空间资源的浪费
    private Hungry(){}

    private final static Hungry HUNGRY=new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}

懒汉式单例

实例虽然作为该类的一个实例变量,但是他不主动进 行创建,如果你不使用它那么他将会永远不被创建,只有你在第一次使用它的时候才会被创建,并且得到保持

package com.study.single;

//懒汉式单例
public class LazyMan {
    //构造器私有
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"启动");
    }
    private static LazyMan lazyMan=null;

    public static LazyMan getInstance(){
        //当被需要时 再去创建
        if (lazyMan==null){
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }
    //单线程下安全 多线程下不安全
    public static void main(String[] args) {
        for (int i = 0; i <10; i++) {
            new Thread(()->{
                lazyMan.getInstance();
            }).start();
        }
    }
}

DCL懒汉式

多线程的情况下,如果线程A和线程B同时判断了lazeMan为null,他们都实例化了,那么就变得很危险了,所以我们加上synchronized锁

    public static LazyMan getInstance(){
        //双重检测锁模式的懒汉式 简称DCL懒汉式
        if (lazyMan==null){
            synchronized(LazyMan.class){
                if (lazyMan==null){
                    lazyMan=new LazyMan();
                }
            }
        }
        return lazyMan;
    }

在lazeMan=new LazeMan这一步时,它不是一个原子性操作,它进行了三步:

第一步:分配内存空间

第二步:执行构造方法,初始化对象

第三步:把这个对象指向这个空间

我们期望他按照123来执行,但他可能是按照132来执行的,那么如果A线程按照132执行完毕,此时B线程来了,由于已经指向了这个空间,他就会认为lazyMan!=null,直接return lazyMan,而这个时候lazeMan还没有完成构造

所以我们需要volatile关键字禁止指令重排优化

private volatile static LazyMan lazyMan=null;

    public static LazyMan getInstance(){
        //双重检测锁模式的懒汉式 简称DCL懒汉式
        if (lazyMan==null){
            synchronized(LazyMan.class){
                if (lazyMan==null){
                    lazyMan=new LazyMan();
                }
            }
        }
        return lazyMan;
    }

完善懒汉式单例

通过反射可以破坏单例模式

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        LazyMan lazyMan1=LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor =
                LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        System.out.println(lazyMan1.hashCode()==lazyMan2.hashCode());
    }

解决:在无参构造器中进行一次判断

private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要试图通过反射来破坏!");
            }
        }
    }

 那当我两个对象都用反射,发现还是被破坏了

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
       // LazyMan lazyMan1=LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor =
                LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        System.out.println(lazyMan1.hashCode()==lazyMan2.hashCode());
    }

解决方法:定义一个标识符

变量名随便定义,初始值为false,那么它第一次执行完,无论通不通过反射,他都会变成true,下一次就会抛出异常,变量名还可以做一些加密,让反射变得更安全

 private static boolean safasfsdfasd=false;
    //构造器私有
    private LazyMan(){
        synchronized (LazyMan.class){
            if (safasfsdfasd==false){
                safasfsdfasd=true;
            }else {
                throw new RuntimeException("不要试图通过反射来破坏!");
            }
        }
    }

那么假设这个变量名被知道了,就又可以进行反射破坏

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
       // LazyMan lazyMan1=LazyMan.getInstance();
        Field safasfsdfasd1 = LazyMan.class.getDeclaredField("safasfsdfasd");//获取字段
        safasfsdfasd1.setAccessible(true);//破坏私有权限
        Constructor<LazyMan> declaredConstructor =
                LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        safasfsdfasd1.set(lazyMan1,false);//把第一个对象的值再改为false
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        System.out.println(lazyMan1.hashCode()==lazyMan2.hashCode());
    }

使用枚举来解决

枚举

package com.study.single;

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

public enum Enum {
    INSTANCE;
    public Enum getInstance(){
        return INSTANCE;
    }

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Enum instance1 = Enum.INSTANCE;
        Constructor<Enum> enumConstructor =
                Enum.class.getDeclaredConstructor(null);
        enumConstructor.setAccessible(true);
        Enum instance2 = enumConstructor.newInstance();
        System.out.println(instance1.hashCode()==instance2.hashCode());
    }

}

我们的报错

正确的报错 

 发现它报错 说没有这个空的构造器,但我们看它的class文件

发现他是有这个空的构造器的 但我们实际执行起来是没有这个空参构造的 它是骗我们的

那么我们进行反编译 它也有一个空参构造 说明它还是骗了我们

 使出我的必杀技 使用更高级的反编译工具 jdp.exe 

 Enum.java

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   Enum.java

package com.study.single;

        import java.io.PrintStream;
        import java.lang.reflect.Constructor;
        import java.lang.reflect.InvocationTargetException;

public final class Enum extends Enum
{

    public static Enum[] values()
    {
        return (Enum[])$VALUES.clone();
    }

    public static Enum valueOf(String name)
    {
        return (Enum)Enum.valueOf(com/study/single/Enum, name);
    }

    private Enum(String s, int i)
    {
        super(s, i);
    }

    public Enum getInstance()
    {
        return INSTANCE;
    }

    public static void main(String args[])
            throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException
    {
        Enum instance1 = INSTANCE;
        Constructor enumConstructor = com/study/single/Enum.getDeclaredConstructor(null);
        enumConstructor.setAccessible(true);
        Enum instance2 = (Enum)enumConstructor.newInstance(new Object[0]);
        System.out.println(instance1.hashCode() == instance2.hashCode());
    }

    public static final Enum INSTANCE;
    private static final Enum $VALUES[];

    static
    {
        INSTANCE = new Enum("INSTANCE", 0);
        $VALUES = (new Enum[] {
                INSTANCE
        });
    }
}

可以看见他只有一个有参构造 String和Int

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Enum instance1 = Enum.INSTANCE;
        Constructor<Enum> enumConstructor =
                Enum.class.getDeclaredConstructor(String.class,int.class);
        enumConstructor.setAccessible(true);
        Enum instance2 = enumConstructor.newInstance();
        System.out.println(instance1.hashCode()==instance2.hashCode());
    }
}

得出正确的结果

反射无法破坏枚举的单例

jad下载地址:JAD Java Decompiler Download Mirror (varaneckas.com)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值