被破坏的单例模式

被破坏的单例模式


我们知道饿汉单例天生是线程安全的,但是通过其他途径单例模式是可以被破坏的。懒汉亦如此。

1、通过反射来破解单例模式

 

 

	public class Eagersingleton implements Serializable {

		private static final long serialVersionUID = 888L;
		private static Eagersingleton m_instance = new Eagersingleton();// 初始化时已经自行实例化

		// 私有默认构造方法
		private Eagersingleton() {
		}

		// 静态工厂方法
		public static Eagersingleton getInstance() {
			return m_instance;
		}
	}
	public class Test1 {

		public static void main(String[] args) throws NoSuchMethodException,
				SecurityException {
			// 获取单例对象
			Eagersingleton instance = Eagersingleton.getInstance();
			Eagersingleton instance2 = null;
			try {
				Class cls = Eagersingleton.class;
				Constructor constructor = cls.getDeclaredConstructor(null);
				// 关掉安全检查,可以调用私有构造器
				constructor.setAccessible(true);
				// 通过反射得到一个对象
				instance2 = (Eagersingleton) constructor.newInstance(null);
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(instance);
			System.out.println(instance2);
		}
	}

代码可以简单归纳为三个步骤:

  • 第一步,获得单例类的构造器。

  • 第二步,把构造器设置为可访问。

  • 第三步,使用newInstance方法构造对象。

  
打印出来的结果不一样,说明这两个对象就是不同的对象,这样就破解了单例模式。
 

解决方案:1.1、避免反射

反射是通过它的Class对象来调用构造器创建出新的对象,我们只需要在构造器中手动抛出异常,导致程序停止就可以达到目的了。

 

1.2、用枚举类实现单例模式(最常用)

public enum SingletonEnum {
    m_instance;
}

注:

  • 防止反射,线程安全。

  • 非使用懒加载,其单例对象在枚举类呗加载的时候就被初始化了。


2、通过序列化和反序列化破解单例
 

	public class Test2 {

		public static void main(String[] args) throws FileNotFoundException,
				ClassNotFoundException {
			// 获取单例对象
			Eagersingleton instance = Eagersingleton.getInstance();

			// 通过反序列化读取对象
			Eagersingleton instance2 = null;
			try {
				ObjectOutputStream oossStream = new ObjectOutputStream(
						new FileOutputStream("D:/EagersingletonTest.txt"));

				// 通过序列化把对象写到文件中
				oossStream.writeObject(instance);
				oossStream.flush();
				oossStream.close();
				// 读取文件的对象
				ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
						"D:/EagersingletonTest.txt"));

				instance2 = (Eagersingleton) ois.readObject();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			System.out.println(instance);
			System.out.println(instance2);
		}
	}

打印出来的结果不一样,说明这两个对象就是不同的对象,破解了单例模式。

解决方案:2.1、避免序列化

readResolve()这个方法是基于回调的,反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象。



3、小纪

 

  • 1. volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值

  • 2.使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。

  • 3.对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。

 

/**
 * 枚举类下的单例模式
 * @author Administrator
 */
public class MySingleton {

    public enum MyEnumSingle {
        INSTANCE;
        
        private MySingleton singleOne;

        private MyEnumSingle() {
            System.out.println("初始化单例");
            singleOne = new MySingleton();
        }

        public MySingleton getInstance() {
            return singleOne;
        }
    }

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        return MyEnumSingle.INSTANCE.getInstance();
    }
}

 





每天努力一点,每天都在进步。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

powerfuler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值