单例模式
单例模式定义:不管在什么场景下只能生成一个类实例;
单例模式主要注意以下几个问题:
1. 线程安全问题;解决方法:sychronized、静态内部类、双重检测时用volatile;
2. 反射破坏单例模式;解决方法:单例构造函数中加对象非空校验;
3. 序列化反序列化破坏单例模式;解决方法:重写readResolve方法或者用枚举单例;
懒汉和饿汉的本质区别,就是实例化对象的时机,即是什么时候将对象创建起来
1 2 3 4 5 6 7 8 | 饿汉式:在加载类的时候就创建单例对象,所以调用时肯定线程安全 缺点是:不管用不用,都会创建对象,消耗内存对象,可能造成浪费(占着茅坑不拉屎) @ThreadSafe public class Hungry { private Hungry() {} private static final Hungry instance = new Hungry(); public static Hungry getInstance() { return instance; } } |
懒汉式单例模式建议使用静态内部类,JVM能够确保线程安全问题,但是要解决反射攻击和序列化反序列化攻击,还得通过代码来实现:
解决反射攻击的问题:需要在构造函数中控制私有静态成员变量的重复创建;
解决序列化反序列化攻击的问题:重写readResolve()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | /** * Created by marcopan on 2018/9/14. * 有反射攻击和序列化攻击的问题 * * 反射攻击解决方法,在构造函数中判断单例是否为空 */ public class LazySingleton implements Serializable { private LazySingleton() { // 防止通过反射来破坏单例 if (LazySingletonHolder.singleton != null ) { throw new RuntimeException( "不允许创建多个实例" ); } } public static final LazySingleton getInstance() { return LazySingletonHolder.singleton; } private static class LazySingletonHolder { private static final LazySingleton singleton = new LazySingleton(); } // public static void main(String[] args) { // try { // //很无聊的情况下,进行破坏 // Class<?> clazz = LazySingleton.class; // //通过反射拿到私有的构造方法 // Constructor c = clazz.getDeclaredConstructor(null); // //强制访问,强吻,不愿意也要吻 // c.setAccessible(true); // // //暴力初始化 // Object o1 = c.newInstance(); // //调用了两次构造方法,相当于new了两次,犯了原则性问题, // Object o2 = LazySingleton.getInstance();//c.newInstance(); // // System.out.println(o1 == o2); // } catch (Exception e) { // e.printStackTrace(); // } // } private Object readResolve() { return LazySingletonHolder.singleton; } public static void main(String[] args) { try { LazySingleton instance1 = LazySingleton.getInstance(); FileOutputStream fos = new FileOutputStream( "EnumSingleton.obj" ); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(instance1); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream( "EnumSingleton.obj" ); ObjectInputStream ois = new ObjectInputStream(fis); LazySingleton instance2 = (LazySingleton) ois.readObject(); ois.close(); System.out.println(instance1 == instance2); } catch (Exception e) { e.printStackTrace(); } } } |
双重校验机制需要添加volatile关键字,防止指令重排序,双重校验机制同样无法解决反射攻击和序列化反序列化的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class RegisterSingleton { /** volatile防止指令重排序 * 有反射和序列化攻击的问题 */ private volatile static RegisterSingleton lazy = null ; private RegisterSingleton() { } public static RegisterSingleton getInstance() { if (lazy == null ) { synchronized (RegisterSingleton. class ) { if (lazy == null ) { lazy = new RegisterSingleton(); //1.分配内存给这个对象 //2.初始化对象 //3.设置lazy指向刚分配的内存地址 //4.初次访问对象 } } } return lazy; } } |
枚举单例模式能够完美解决反射攻击和序列化反序列化的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public enum EnumSingleton { INSTANCE; private Object data; public Object getData() { return data; } public void setData(Object data) { this .data = data; } public static EnumSingleton getInstance() { return INSTANCE; } public static void main(String[] args) { try { EnumSingleton instance1 = EnumSingleton.getInstance(); instance1.setData( new Object()); FileOutputStream fos = new FileOutputStream( "EnumSingleton.obj" ); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(instance1); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream( "EnumSingleton.obj" ); ObjectInputStream ois = new ObjectInputStream(fis); EnumSingleton instance2 = (EnumSingleton) ois.readObject(); ois.close(); System.out.println(instance1.getData()); System.out.println(instance2.getData()); System.out.println(instance1.getData() == instance2.getData()); } catch (Exception e) { e.printStackTrace(); } } } |
其中关于枚举的可以参考这篇文章,写得挺透彻的http://www.manongjc.com/article/1597.html
这里还要强调一点关于双重检查锁的情况,双重检查锁之所以是线程不安全的,原因在于CPU处理器会对指令进行重排序,有线程可能会拿到没有完成初始化的对象。双重检查锁要使用volatile变量避免双重检查锁线程不安全的问题
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步