对于Java来说单例模式可以有以下几种方式:
- 饿汉方式
- 懒汉方式
- 双重检查加锁懒汉方式
- 内部类方式
- 枚举方式
破解单例模式有两种方式:通过反射的方式和通过序列化的方式。下面将一一对此进行分析。
饿汉方式非常简单,即使用一个初始化的静态变量,代码如下:
1 |
public class EagerSingleton {
|
2 |
private static final EagerSingleton instance = new EagerSingleton();
|
4 |
private EagerSingleton(){}
|
6 |
public static EagerSingleton getInstance() {
|
懒汉模式和饿汉模式类似,只是静态变量定义时不进行初始化,调用getInstance()时才进行初始化,这就需要考虑多线程时问题,使用synchronized关键字修饰方法即可:
02 |
private static LazySingleton instance = null ;
|
03 |
private LazySingleton() {}
|
04 |
public static synchronized LazySingleton getInstance() {
|
05 |
if (instance == null ) {
|
06 |
instance = new LazySingleton();
|
懒汉模式实际是一种懒加载,但是为了避免多线程时单例失效,必须对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();
|
这个需要几个注意点:静态类变量必须声明为private static volatile,这样可以 写入操作 happens-before 于每一个后续的同一个字段的读操作。在getInstance()方法中,第一次判断null后,使用同步防止多线程时破坏单例。
第四种是使用内部类的方式,也是一种懒加载的方式实现:
1 |
class InnerClassLazySingleton {
|
2 |
private static class SingletonCreator {
|
3 |
private static final InnerClassLazySingleton instance = new InnerClassLazySingleton();
|
5 |
public static InnerClassLazySingleton getInstance() {
|
6 |
return SingletonCreator.instance;
|
内部类只在第一次调用的时候才会被类加载器加载,实现了懒加载
同时由于instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性
但是上面四种方式都可以通过反射的方式来破坏单例:
1 |
InnerClassLazySingleton eagerSingleton = InnerClassLazySingleton.getInstance(); |
2 |
Constructor<InnerClassLazySingleton> constructor = InnerClassLazySingleton. class .getDeclaredConstructor();
|
3 |
constructor.setAccessible( true );
|
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); |
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() {
|
08 |
private Object readResolve() {
|
09 |
System.out.println( "print from readResolve() method" );
|
13 |
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
14 |
System.out.println( "print from readObject() method" );
|
15 |
ois.defaultReadObject();
|
readResolve()方法是用来替换从流中读取的对象的,它在readObject(ObjectInputStream)方法之后被调用,readObject()方法即从流中读取对象的方法。这样就可以避免使用序列化方式破坏单例。
但是上面的四种方式还是无法避免反射的方式来破坏单例的情况,可以使用枚举的方式实现单例:
4 |
public static void method() {
|
通过反射方式创建实例时会抛出 Exception in thread "main" java.lang.NoSuchMethodException: net.local.singleton.EnumSingleton.<init>() 异常。并且使用枚举类Enum实现了序列化接口,并且也实现了readResolve()方法,因为使用序列化方式也没法破坏,是最理想的单例模式实现。
分享到:
相关推荐
DCL模式通过在实例化单例时使用 volatile 关键字和双层检查,解决了这个问题。volatile关键字保证了多线程环境下变量的可见性,避免了指令重排序,从而保证了单例的正确创建。 此外,单例模式还有几种变体,比如...
单例模式的反射漏洞和反序列化漏洞代码实例 单例模式是软件设计模式中一种常用的设计模式,用于限制一个类的实例化次数,确保一个类在整个应用程序中只有一个实例。然而,单例模式存在一些漏洞,例如反射漏洞和反...
然而,如果涉及类加载器或跨JVM的场景,单例模式的实现就需要更复杂的策略,例如使用`序列化`和`克隆`时需要特殊处理,防止生成额外的实例。另外,如果要考虑服务集群或分布式系统,可能需要采用分布式单例,例如...
此外,通过使用枚举方式实现单例,可以防止反射和序列化带来的多实例问题。 总结来说,单例模式是一种重要的设计模式,用于控制类实例的数量,以优化资源管理和提高效率。在实际开发中,我们需要根据具体需求选择...
它天然地线程安全,无法被反射或序列化破坏。代码示例: ```java public enum Singleton { INSTANCE; } ``` 这种方式简洁、高效且易于理解,同时避免了上述所有问题。 总结,选择哪种单例模式取决于具体需求...
枚举的单例模式简单易懂,且天然线程安全,不会受到反射和序列化攻击的影响,是推荐的单例实现方式之一。 总的来说,单例模式是一种常见的设计模式,懒汉式单例模式则是其中一种实现策略,它的主要特点是延迟加载和...
这是Joshua Bloch在《Effective Java》中推荐的方法,枚举的实例化是线程安全的,并且自动防止了反射和序列化攻击。 每种模式都有其适用场景,例如,如果对性能和内存占用非常敏感,可以考虑使用饿汉模式;而在...
枚举的单例在Java中被认为是最好的实现方式,因为它不仅线程安全,而且防止了序列化和反射攻击。 每种实现方式都有其特点和适用场景,开发者应根据实际需求选择合适的单例实现。在实际项目中,还要考虑性能、线程...
而如果考虑到反射攻击和序列化问题,可能需要选择DCL或枚举实现。在使用单例模式时,还需要注意以下几点: - 单例模式可能导致程序设计过度集中,不利于模块化。 - 序列化时需谨慎处理,否则可能会生成多个实例。 - ...
最安全且最简单的实现方式,防止反射攻击,同时避免了序列化带来的问题。 ```java public enum Singleton { INSTANCE; public void whateverMethod() { } } ``` 以上就是Java中常见的单例模式实现方式,每...
2. **对象复制问题**:需要防止通过序列化或反射机制创建额外的实例,这可能破坏单例模式的一致性。 ### 设计思想 单例模式的设计思想主要体现在对“单个实例”和“全局访问点”的追求上。通过将构造函数私有化并...
5. **枚举**:JDK5之后引入的一种更简单的单例实现方式,天然支持序列化机制,能避免反射和反序列化的攻击。 ```java public enum Singleton { INSTANCE; public void showMessage() { System.out.println(...
枚举天生就是线程安全的,无法被反射或序列化破坏。 ```java public enum SingleInstance4 { INSTANCE; public void singletonMethod() { // ... } } ``` 5. 静态内部类单例(Static Nested Class): 这种...
4. 如何通过反射或序列化破坏单例模式的唯一性,以及如何防御? 5. 如何在保证单例模式的同时,增加可配置性或者实现不同实例的切换? 理解并熟练掌握单例模式的实现方式和应用场景,对于编写高效、可维护的代码至...
这种方式既保证了线程安全,又避免了反射攻击和序列化攻击等问题。 ```java public enum Singleton { INSTANCE; } ``` 总的来说,单例模式在Java中有着重要的应用,开发者可以根据实际需求选择适合的实现方式。...
单例模式是软件设计模式中的一种,属于创建型模式。...综上所述,单例模式的实现和应用在软件开发中占有重要位置,但同时也需要开发者警惕一些实现细节,确保单例模式在各种场景下的有效性和可靠性。
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。