我们可以在Java中打破单例模式的不同方法有哪些

时间:2013-12-06 10:44:04

标签: java singleton

我们可以在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;
    }
}

10 个答案:

答案 0 :(得分:10)

从您给定的代码开始,“Double-Checked Locking”可能会在某些环境中被破坏, 在使用Symantec JIT的系统上运行时,它不起作用。特别是,Symantec JIT编译

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

以下内容(请注意Symantec 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

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

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

除此之外

  1. 如果课程为Serializable
  2. ,则会中断
  3. 如果它的'Clonable`
  4. 可能会破坏
  5. 你可以突破Reflection(我相信)
  6. 它可以打破ff多个类加载器加载类

  7. * 您如何解决规则破坏者?

    1. 进行热切初始化更安全
    2. 防止反序列化以创建新对象 您可以覆盖类中的readResolve()方法并抛出异常
    3. 为了防止克隆, 您可以覆盖clone()并抛出CloneNotSupported例外
    4. 要逃避反射实例,我们可以在构造函数中添加check并抛出异常。
    5. 示例

      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;
          }
      }
      

答案 1 :(得分:3)

实际上,无需同步的安全版本为the version with a nested holder class

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();
  }
}

其他安全版本是:

  • Eager initialization

    public final class Singleton{
        public static final Singleton getInstance(){
            return INSTANCE;
        }
        private Singleton();
        private static final Singleton INSTANCE = new Singleton();
    }
    
  • Enum Singleton

    public enum Singleton{
        INSTANCE;
    }
    

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

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

答案 2 :(得分:2)

一种方法是序列化。如果你没有实现readResolve,那么用ObjectInputStream.readObject()读取一个单例将返回这个单例的一个新实例。

答案 3 :(得分:1)

AFAIK,有两种方法可以打破

  • 使用反思

  • 当有自定义类加载器时,多个(即父类加载器)。所有单例应该由公共父类加载器加载。

答案 4 :(得分:1)

import java.io.Serializable;

public class Singleton implements Serializable,Cloneable{

private static final long serialVersionUID = 1L;

private static Singleton singleton=null;
private Singleton(){

}

public static Singleton getInstance(){
    if(singleton==null){
        singleton=new Singleton();
    }
    return singleton;   
}

@Override
public Object clone() throws CloneNotSupportedException{
    return super.clone();
}
}

**单身人士测试**

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/***
 * 
 * Ways to break Singleton
 */
public class Main {

private static ObjectInputStream inputStream;

public static void main(String[] args) throws Exception {
    Singleton orginalSingletonObject = Singleton.getInstance();

    /***
     * Singleton is broken by using Reflection
     */
    breakSingletonByReflection(orginalSingletonObject);

    /***
     * By Serialization/De-Serialization break Singleton We need
     * Serialization interface in a class nedds to be serialized like
     * Singleton.java
     */
    breakSingletonByserialization(orginalSingletonObject);

    /***
     * By Cloning break Singleton
     * We need to implement Cloneable interface
     */
    breakSingletonByCloning(orginalSingletonObject);


    /***
     * Break Singleton By thread
     * This scenario is related to multi-threading environment
     * 
     */

    breakSingletonByThreading(orginalSingletonObject);
}

private static void breakSingletonByThreading(Singleton orginalSingletonObject) {

    ExecutorService executorService=Executors.newFixedThreadPool(2);
    /**
     * Run this code snippet after commenting the other code for better understanding
     * Run it repeatly to create a condition when 2 threads enter the method getInstance() of Singleton class at a same time 
     * When 2 threads enter the getInstance method at same time they will get the singleton object as null (private static Singleton singleton in Singleton.java)
     * Then they will create two different objects ( have different hashcode) in this case singleton pattern will break.
     */
    executorService.submit(Main::useSingleton); // JAVA 8 syntax it will get the singleton instance
    executorService.submit(Main::useSingleton);
    executorService.shutdown();
}

public static void useSingleton(){
    Singleton singleton=Singleton.getInstance();
    printSingletonData("By Threading", singleton);

}


private static void breakSingletonByCloning(Singleton orginalSingletonObject) throws CloneNotSupportedException {
    Singleton clonedSingletonObject=(Singleton) orginalSingletonObject.clone();
    printSingletonData("By Cloning", orginalSingletonObject, clonedSingletonObject);
}

private static void breakSingletonByReflection(Singleton orginalsingleton)
        throws ClassNotFoundException, NoSuchMethodException,
        InstantiationException, IllegalAccessException,
        InvocationTargetException {

    Class<?> singletonClass = Class.forName("SingletonTest.Singleton");
    @SuppressWarnings("unchecked")
    Constructor<Singleton> constructor = (Constructor<Singleton>) singletonClass
            .getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton s = constructor.newInstance();
    printSingletonData("By Reflection", orginalsingleton, s);
}

private static void breakSingletonByserialization(Singleton orginalsingleton)
        throws FileNotFoundException, IOException, ClassNotFoundException {

    /**
     * Serialization
     */
    ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("E:\\Singleton.ser"));
    outputStream.writeObject(orginalsingleton);
    outputStream.close();

    /**
     * DeSerialization
     */
    inputStream = new ObjectInputStream(new FileInputStream("E:\\Singleton.ser"));

    Singleton deserializeObject = (Singleton) inputStream.readObject();
    deserializeObject.hashCode();
    printSingletonData("By Serialization", orginalsingleton, deserializeObject);


}

public static void printSingletonData(String operationName,
        Singleton orginalsingleton, Singleton reflectionSigletonObject) {

    System.out.println("------------------------------------------");
    System.out.println("New Operation");
    System.out.println(operationName);
    System.out.println("orginal Hashcode=" + orginalsingleton.hashCode());
    System.out.println("New Object hashcode="
            + reflectionSigletonObject.hashCode());
    Boolean value = orginalsingleton.hashCode() != reflectionSigletonObject.hashCode();
    System.out.println("These Object have different hascode. They are two different object Right = "
                    + value);
    System.out.println("As these are different Object this means Singleton Pattern is broken");
}


private static void printSingletonData(String operationName,Singleton singleton) {


    System.out.println("------------------------------------------");
    System.out.println("New Operation");
    System.out.println(operationName);
    System.out.println("Object hashcode="   + singleton.hashCode());

}
}

答案 5 :(得分:1)

Class cls = Singleton.class;

    Constructor constructor = cls.getDeclaredConstructor();
    constructor.setAccessible(true);

    Singleton singleton = (Singleton) constructor.newInstance();

答案 6 :(得分:0)

多线程是单身人士面临的最大问题。 您可以通过同步或使用急切初始化来避免这种情况。

另一种方法是使用你不应该使用的单身人士。通过使用单例模式,您可以在以后的开发中以一种阻碍程序的方式应用它。 (E.G,在游戏中创建单身“玩家”,因为你认为它是单人游戏。开发的下一步是“添加合作功能”)。

单身模式有它的优点,但如果不仔细考虑就不要使用它。

答案 7 :(得分:0)

首先注意:在这种情况下,主要是为了提高可读性,可以更好地制作整个getSingleInstance() { } synchronized

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

除此之外,我认为打破代码并不容易。当然,如果你添加递归调用,那么可以打破它,如下所示:

  • 在此Singleton的构造函数中调用其他类的构造函数。
  • 在该类的构造函数中,尝试获取此Singleton实例。

但这是我唯一可以考虑的事情,你无法在Singleton课程中保护自己。

答案 8 :(得分:0)

同步方法将起作用,但也会降低对单例的每次访问速度,以保护只在第一次访问时发生的事情。

最简单和最安全的方法就是进行急切的初始化,这始终是安全的,因为Java保证在允许任何人访问它们之前设置所有成员变量。

public class Singleton {
    private static Singleton singleInstance = new Singleton();

    private Singleton() {
    }

    public static Singleton getSingleInstance() {
        return singleInstance;
    }
}

即使使用同步循环,您当前的方法实际上已被破坏 - 因为双重检查锁定已被破坏。如果要将其用于双重检查锁定,则需要将单例变量标记为volatile,否则线程仍有办法访问未完全初始化的对象。有关详细信息,请参阅http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java

答案 9 :(得分:0)

您可以浏览此链接,有不同的方法来中断和阻止单例类:

Ways to break/prevent singleton class