在Java中,我们可以通过哪些不同的方式打破单例模式

2022-09-04 01:47:57

在Java中,我们可以通过哪些不同的方式打破单例模式。我知道一种方法,即如果我们不同步单例中的方法,那么我们可以创建多个类的实例。因此,将应用同步。有没有办法打破单例java类。

public class Singleton {
    private static Singleton singleInstance;

    private Singleton() {
    }

    public static Singleton getSingleInstance() {
        if (singleInstance == null) {
            synchronized (Singleton.class) {
                if (singleInstance == null) {
                    singleInstance = new Singleton();
                }
            }
        }
        return singleInstance;
    }
}

答案 1

从给定的代码开始,“双重检查锁定”在某些环境中可能会损坏,当使用赛门铁克JIT在系统上运行时,它不起作用。特别是,赛门铁克 JIT 可编译

singletons[i].reference = new Singleton();

到下面(请注意,赛门铁克 JIT 使用基于句柄的对象分配系统)。

0206106A   mov         eax,0F97E78h
0206106F   call        01F6B210                  ; allocate space for
                                                 ; Singleton, return result in eax
02061074   mov         dword ptr [ebp],eax       ; EBP is &singletons[i].reference 
                                                ; store the unconstructed object here.
02061077   mov         ecx,dword ptr [eax]       ; dereference the handle to
                                                 ; get the raw pointer
02061079   mov         dword ptr [ecx],100h      ; Next 4 lines are
0206107F   mov         dword ptr [ecx+4],200h    ; Singleton's inlined constructor
02061086   mov         dword ptr [ecx+8],400h
0206108D   mov         dword ptr [ecx+0Ch],0F84030h

如您所见,对单例[i].引用的赋值是在调用单例构造函数之前执行的。这在现有的Java内存模型下是完全合法的,在C和C++中也是合法的(因为它们都没有内存模型)。

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

除此之外

  1. 如果类是Serializable
  2. 如果它的“可克隆”,它可能会中断
  3. 你可以打破(我相信)Reflection
  4. 它可以打破ff多个类装入器被装入类

*如何解决规则破坏者?

  1. 进行预先初始化要安全得多
  2. 为了防止反序列化以创建新对象,您可以在类中重写方法并引发异常readResolve()
  3. 为了防止克隆,您可以过度引发并引发异常clone()CloneNotSupported
  4. 为了转义反射瞬时,我们可以在构造函数中添加check并抛出异常。

public class Singleton {
 
    private static final Singleton INSTANCE = new Singleton();
 
    private Singleton() {
        // Check if we already have an instance
        if (INSTANCE != null) {
           throw new IllegalStateException("Singleton" +
             " instance already created.");
        }
    }
    public static final Singleton getInstance() {
        return INSTANCE;
    }
    private Object readResolve() throws ObjectStreamException         {
            return INSTANCE;
    }
    private Object writeReplace() throws ObjectStreamException {
            return INSTANCE;
    }
    public Object clone() throws CloneNotSupportedException {
        // return INSTANCE
        throw new CloneNotSupportedException();
    }
}

毕竟,我建议使用Enum作为Singleton最安全的方式(因为java5最好的方法是使用枚举)

public static enum SingletonFactory {
    INSTANCE;
    public static SingletonFactory getInstance() {
        return INSTANCE;
    }
}

答案 2

实际上,不需要同步的安全版本是具有嵌套持有者类的版本

public final class Singleton{

  public static final Singleton getInstance(){
    // no need for synchronization since the classloader guarantees to initialize
    // Holder.INSTANCE exactly once before handing out a reference to it
    return Holder.INSTANCE;
  }
  private Singleton();
  private static class Holder{
    private static final Singleton INSTANCE = new Singleton();
  }
}

其他安全版本包括:

  • 急切的初始化

    public final class Singleton{
        public static final Singleton getInstance(){
            return INSTANCE;
        }
        private Singleton();
        private static final Singleton INSTANCE = new Singleton();
    }
    
  • 枚举辛格尔顿

    public enum Singleton{
        INSTANCE;
    }
    

所有这些版本都有优点和缺点,但它们都不需要显式同步,因为它们都依赖于 ClassLoader 及其内置的线程安全。

正如其他人所写的那样,您可以通过反序列化来打破其中一些模式。阅读 Joshua Bloch(第 74 至 78 项)撰写的《有效 Java》,了解如何防止此类攻击(枚举单例模式开箱即用,可安全抵御此类攻击)。


推荐