设计模式-单例模式

举报
AAAI 发表于 2021/05/05 01:22:30 2021/05/05
3.2k+ 0 0
【摘要】 单例模式属于创建者模式。 单例模式 饿汉式,懒汉式,单例模式特点就是构造器私有化 饿汉式单例 有可能浪费内存空间 public class Hungry{ private Hungry(){} private final static Hungry HUNGRY = new Hungry(); // private final static Singleton...

单例模式属于创建者模式。

单例模式

饿汉式,懒汉式,单例模式特点就是构造器私有化

饿汉式单例

有可能浪费内存空间

public class Hungry{ private Hungry(){} private final static Hungry HUNGRY = new Hungry(); // private final static Singleton = new Singleton() publci static Hungry getInstance(){ return Hungry(); }
}

  
 

其实如果这个类在项目中不因getInstance之外的原因被提前加载,饿汉是没问题的。把new Hungry写在static代码块里,依赖ClassLoader初始化它的时候进行new。

懒汉式单例

DCL,构造器私有化

public class LazyMan{ private LazyMan(){} private final static LazyMan LAZYMAN; public static LazyMan getInstance(){ if (LazyMan == null){ LazyMan = new LazyMan(); } return LazyMan(); }
}

  
 

但是在多线程并发时,也会出现错误。

静态内部类

public class Holder{ private Holder(){ } public static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass{ private static final Honder HONDER = new Honder(); }
}

  
 

DCL懒汉式

因此需要加锁。-- DCL懒汉式

public static void main(String[] args){ for (int i=0; i<10; i++){ new Thread(()->{ LazyMan.getInstance(); }).start(); }
}

  
 
public class LazyMan{ private LazyMan(){} // volatile 避免指令重排 // 1.分配内存空间 // 2.执行构造方法,初始化对象 // 3.将对象指向这个空间 // 以上会在CPU加载时随机进行变换顺序,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化。 private volatile final static LazyMan LAZYMAN; // 双重检测锁模式的懒汉式单例  DCL模式 public static LazyMan getInstance(){ if (LazyMan == null){ synchronized(LazyMan.class){ // 对类对象进行上锁,保证当前类对象只有一个。第一层保证线程安全,只有一个实例对象生成。 if (LazyMan == null){ LazyMan = new LazyMan(); } } } return LazyMan(); }
}

  
 

但是,使用反射可以进行破坏单例。单例只能有一个对象,反射机制破坏了它,重新创造了一个对象,所以地址空间不同,hashcode肯定也不同。

public static void main(String[] args) throws Exception{ LazyMan instance = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 获取构造器 declaredConstructor.setAccessible(true); //无视私有属性 LazyMan instance1 = declaredConstructor.newInstance(); // 执行类的构造器的方法
}

  
 

解决方法:在构造方法里也进行线程判断。

private LazyMan(){ synchronized(LazyMan.class){ if (LazyMan != null){ throw new RuntimeException("Xxx"); } }
 }

  
 

破坏方法:多次使用反射,进行使用构造方法,两次的hashcode不同,即不是同一个对象。因为,不通过getInstance的话new出来的对象的指针不会交给LazyMan。lazyMan一直是null值。

LazyMan instance = declaredConstructor.newInstance();
LazyMan instance1 = declaredConstructor.newInstance();

  
 

解决方法:红绿灯,使用标记位进行记录。做个标记,一旦创建对象就标记。

private static boolean xx = false;  
private LazyMan(){ synchronized(LazyMan.class){ if (xx == false){ xx = true; } else { throw new RuntimeException("Xxx"); } }
 }

  
 

破坏方法:找到标记位,更改标记位。此时instance和instance1的hashcode又一样了。

LazyMan instance = declaredConstructor.newInstance();
Field xx = LazyMan.class.getDeclaredField("xx"); // 找到xx字段
xx.setAccessible(true);// 破坏私有属性;
xx.set(instance, false);
LazyMan instance1 = declaredConstructor.newInstance();

  
 

解决方法:使用枚举enum,自带单例。在源码中写到,反射不能破坏枚举。

if ((class.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("不能用反射创建枚举对象");

  
 

enum就和class一样,只是一个关键字,他并不是一个类。

枚举

public enum EnumSingle{ INSTANCE; public EnumSingle getInstance({ return INSTANCE; })
}
class Test{ public static void main(String[] args){ EnumSingle instance  = EnumSingle.INSTANCE; EnumSingle instance1  = EnumSingle.INSTANCE; }
}

  
 

使用反编译进行查看EnumSingle.class 源码:javap -p EnumSingle.class,发现EnumSingle.class里面的构造方法是应该有参方法,EnumSingle(String s, int i)

 Constructor<LazyMan> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class); 

  
 

文章来源: blog.csdn.net,作者:αβγθ,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_38022166/article/details/116405635

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。