`
liuluo129
  • 浏览: 116492 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

单例模式以及通过反射和序列化破解单例模式

 
阅读更多

对于Java来说单例模式可以有以下几种方式:

  1. 饿汉方式
  2. 懒汉方式
  3. 双重检查加锁懒汉方式
  4. 内部类方式
  5. 枚举方式

破解单例模式有两种方式:通过反射的方式和通过序列化的方式。下面将一一对此进行分析。

饿汉方式非常简单,即使用一个初始化的静态变量,代码如下:

1 public class EagerSingleton {
2    private static final EagerSingleton instance = new EagerSingleton();
3  
4    private EagerSingleton(){}
5  
6    public static EagerSingleton getInstance() {
7       return instance;
8    }
9 }

懒汉模式和饿汉模式类似,只是静态变量定义时不进行初始化,调用getInstance()时才进行初始化,这就需要考虑多线程时问题,使用synchronized关键字修饰方法即可:

01 class LazySingleton {
02     private static LazySingleton instance = null;
03     private LazySingleton() {}
04     public static synchronized LazySingleton getInstance() {
05         if (instance == null) {
06             instance = new LazySingleton();
07         }
08         return instance;
09     }
10 }

懒汉模式实际是一种懒加载,但是为了避免多线程时单例失效,必须对getInstance()方法进行同步。可以使用双重检查方式来避免对方法全部进行加锁:

01 class DoubleCheckSingleton {
02     private static volatile DoubleCheckSingleton instance = null;
03     private DoubleCheckSingleton() {}
04     public static DoubleCheckSingleton getInstance() {
05         if (instance == null) {
06             synchronized (DoubleCheckSingleton.class) {
07                 if (instance == null) {
08                     instance = new DoubleCheckSingleton();
09                 }
10             }
11         }
12         return instance;
13     }
14 }

这个需要几个注意点:静态类变量必须声明为private static volatile,这样可以 写入操作 happens-before 于每一个后续的同一个字段的读操作。在getInstance()方法中,第一次判断null后,使用同步防止多线程时破坏单例。

 

第四种是使用内部类的方式,也是一种懒加载的方式实现:

 

1 class InnerClassLazySingleton {
2     private static class SingletonCreator {
3         private static final InnerClassLazySingleton instance = new InnerClassLazySingleton();
4     }
5     public static InnerClassLazySingleton getInstance() {
6         return SingletonCreator.instance;
7     }
8 }

内部类只在第一次调用的时候才会被类加载器加载,实现了懒加载

同时由于instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性

但是上面四种方式都可以通过反射的方式来破坏单例:

1 InnerClassLazySingleton eagerSingleton = InnerClassLazySingleton.getInstance();
2 Constructor<InnerClassLazySingleton> constructor = InnerClassLazySingleton.class.getDeclaredConstructor();
3 constructor.setAccessible(true);
4  
5 InnerClassLazySingleton eagerSingletonFromRef = constructor.newInstance();
6 System.out.println("使用反射破解饥渴单例模式:" + (eagerSingleton == eagerSingletonFromRef ? "否" "是"));

并且如果实现了Serializable接口的话,通过序列化的方式也可以破坏单例:

1 ByteArrayOutputStream baos  = new ByteArrayOutputStream();
2 ObjectOutputStream oos = new ObjectOutputStream(baos);
3 oos.writeObject(eagerSingleton);
4  
5 ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
6 InnerClassLazySingleton eagerSingletonFromSerial =  (InnerClassLazySingleton) ois.readObject();
7 System.out.println("使用序列化破解饥渴单例模式:" + (eagerSingleton == eagerSingletonFromSerial ? "否" "是"));

可以通过在单例类中添加readResolve()方法的方式来解决:

01 class EagerSingleton implements Serializable {
02     private static final EagerSingleton instance = new EagerSingleton();
03     private EagerSingleton() {}
04     public static EagerSingleton getInstance() {
05         return instance;
06     }
07  
08     private Object readResolve() {
09         System.out.println("print from readResolve() method");
10         return getInstance();
11     }
12  
13     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
14         System.out.println("print from readObject() method");
15         ois.defaultReadObject();
16     }
17 }

readResolve()方法是用来替换从流中读取的对象的,它在readObject(ObjectInputStream)方法之后被调用,readObject()方法即从流中读取对象的方法。这样就可以避免使用序列化方式破坏单例。

 

但是上面的四种方式还是无法避免反射的方式来破坏单例的情况,可以使用枚举的方式实现单例:

1 enum EnumSingleton {
2     INSTANCE;
3      
4     public static void method() {
5          
6     }
7 }

通过反射方式创建实例时会抛出 Exception in thread "main" java.lang.NoSuchMethodException: net.local.singleton.EnumSingleton.<init>() 异常。并且使用枚举类Enum实现了序列化接口,并且也实现了readResolve()方法,因为使用序列化方式也没法破坏,是最理想的单例模式实现。 

分享到:
评论

相关推荐

    设计模式之单例模式(结合工厂模式)

    DCL模式通过在实例化单例时使用 volatile 关键字和双层检查,解决了这个问题。volatile关键字保证了多线程环境下变量的可见性,避免了指令重排序,从而保证了单例的正确创建。 此外,单例模式还有几种变体,比如...

    单例模式的反射漏洞和反序列化漏洞代码实例

    单例模式的反射漏洞和反序列化漏洞代码实例 单例模式是软件设计模式中一种常用的设计模式,用于限制一个类的实例化次数,确保一个类在整个应用程序中只有一个实例。然而,单例模式存在一些漏洞,例如反射漏洞和反...

    单例模式详解~~单例模式详解~~

    然而,如果涉及类加载器或跨JVM的场景,单例模式的实现就需要更复杂的策略,例如使用`序列化`和`克隆`时需要特殊处理,防止生成额外的实例。另外,如果要考虑服务集群或分布式系统,可能需要采用分布式单例,例如...

    单例模式(singleton)

    此外,通过使用枚举方式实现单例,可以防止反射和序列化带来的多实例问题。 总结来说,单例模式是一种重要的设计模式,用于控制类实例的数量,以优化资源管理和提高效率。在实际开发中,我们需要根据具体需求选择...

    单例模式(饿汉模式、懒汉模式、DCL单例模式、枚举)

    它天然地线程安全,无法被反射或序列化破坏。代码示例: ```java public enum Singleton { INSTANCE; } ``` 这种方式简洁、高效且易于理解,同时避免了上述所有问题。 总结,选择哪种单例模式取决于具体需求...

    设计模式——单例模式(懒汉模式)

    枚举的单例模式简单易懂,且天然线程安全,不会受到反射和序列化攻击的影响,是推荐的单例实现方式之一。 总的来说,单例模式是一种常见的设计模式,懒汉式单例模式则是其中一种实现策略,它的主要特点是延迟加载和...

    单例模式案例和笔记,通过案例来了解单例模式

    这是Joshua Bloch在《Effective Java》中推荐的方法,枚举的实例化是线程安全的,并且自动防止了反射和序列化攻击。 每种模式都有其适用场景,例如,如果对性能和内存占用非常敏感,可以考虑使用饿汉模式;而在...

    单例模式各种实现方式

    枚举的单例在Java中被认为是最好的实现方式,因为它不仅线程安全,而且防止了序列化和反射攻击。 每种实现方式都有其特点和适用场景,开发者应根据实际需求选择合适的单例实现。在实际项目中,还要考虑性能、线程...

    Java单例模式深入理解

    而如果考虑到反射攻击和序列化问题,可能需要选择DCL或枚举实现。在使用单例模式时,还需要注意以下几点: - 单例模式可能导致程序设计过度集中,不利于模块化。 - 序列化时需谨慎处理,否则可能会生成多个实例。 - ...

    设计模式-单例模式

    最安全且最简单的实现方式,防止反射攻击,同时避免了序列化带来的问题。 ```java public enum Singleton { INSTANCE; public void whateverMethod() { } } ``` 以上就是Java中常见的单例模式实现方式,每...

    单例设计模式的优缺点和设计思想

    2. **对象复制问题**:需要防止通过序列化或反射机制创建额外的实例,这可能破坏单例模式的一致性。 ### 设计思想 单例模式的设计思想主要体现在对“单个实例”和“全局访问点”的追求上。通过将构造函数私有化并...

    单例模式 Singleton Pattern

    5. **枚举**:JDK5之后引入的一种更简单的单例实现方式,天然支持序列化机制,能避免反射和反序列化的攻击。 ```java public enum Singleton { INSTANCE; public void showMessage() { System.out.println(...

    Java实现多种单例模式

    枚举天生就是线程安全的,无法被反射或序列化破坏。 ```java public enum SingleInstance4 { INSTANCE; public void singletonMethod() { // ... } } ``` 5. 静态内部类单例(Static Nested Class): 这种...

    常见设计模式-单例模式

    4. 如何通过反射或序列化破坏单例模式的唯一性,以及如何防御? 5. 如何在保证单例模式的同时,增加可配置性或者实现不同实例的切换? 理解并熟练掌握单例模式的实现方式和应用场景,对于编写高效、可维护的代码至...

    Java实现单例模式[汇编].pdf

    这种方式既保证了线程安全,又避免了反射攻击和序列化攻击等问题。 ```java public enum Singleton { INSTANCE; } ``` 总的来说,单例模式在Java中有着重要的应用,开发者可以根据实际需求选择适合的实现方式。...

    设计模式_创建型_单例模式.md

    单例模式是软件设计模式中的一种,属于创建型模式。...综上所述,单例模式的实现和应用在软件开发中占有重要位置,但同时也需要开发者警惕一些实现细节,确保单例模式在各种场景下的有效性和可靠性。

    实验12 单例模式与枚举.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

Global site tag (gtag.js) - Google Analytics