设计模式-单例模式
【摘要】 单例模式属于创建者模式。
单例模式
饿汉式,懒汉式,单例模式特点就是构造器私有化
饿汉式单例
有可能浪费内存空间
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)