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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20421920/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 01:50:22  来源:igfitidea点击:

What are the different ways we can break a singleton pattern in Java

javasingleton

提问by Harsha_2012

What are the different ways we can break a singleton pattern in Java. I know one way i.e. if we do not synchronize the method in singleton , then we can create more than an instance of the class. So synchronization is applied. Is there a way to break singleton java class.

我们可以通过哪些不同的方式打破 Java 中的单例模式。我知道一种方法,即如果我们不在 singleton 中同步方法,那么我们可以创建多个类的实例。因此应用了同步。有没有办法打破单例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;
    }
}

回答by Dylan Meeus

Multithreading is the biggest issue with singletons. You can avoid that by synchronizing or using eager initialisation.

多线程是单例最大的问题。您可以通过同步或使用预先初始化来避免这种情况。

Another way would be to use singleton where you're not supposed to. By using the singleton pattern you might apply it in a way that obstructs your program in later development. (E.G, creating a singleton "player" in a game, because you assume it's a singleplayer game. Next step in development "add co-op functionality").

另一种方法是在不应该使用的地方使用单例。通过使用单例模式,您可能会以某种方式应用它,从而在以后的开发中阻碍您的程序。(例如,在游戏中创建单人“玩家”,因为您假设它是单人游戏。开发的下一步“添加合作功能”)。

The singleton pattern has it's advantages, but don't use it without carefull consideration.

单例模式有它的优点,但不要在没有仔细考虑的情况下使用它。

回答by Keerthivasan

AFAIK, there are two ways it could be broken

AFAIK,有两种方法可以破解

  • Using Reflection

  • When there are custom classloaders, more than one (i.e. parent class loader).all singletons should be loaded by common parent classloader.

  • 使用反射

  • 当有自定义类加载器时,不止一个(即父类加载器)。所有的单例都应该由共同的父类加载器加载。

回答by skiwi

A note first: It would be better in this case, mainly for readability, to make the whole getSingleInstance() { }synchronized:

首先注意:在这种情况下,主要是为了可读性,最好使整体getSingleInstance() { }synchronized

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

Other than that, I don't think it is easy to break the code. Of course if you add recursive calls then it is possible to break it, like this:

除此之外,我认为破解代码并不容易。当然,如果您添加递归调用,则可能会破坏它,如下所示:

  • Call the constructor of some other class inside this Singleton's constructor.
  • In the constructor of that class, try to obtain this Singletoninstance.
  • 在 thisSingleton的构造函数中调用其他类的构造函数。
  • 在那个类的构造函数中,尝试获取这个Singleton实例。

But that's the only thing I can think about, and you cannot protect yourself from that in the Singletonclass.

但这是我唯一能想到的,你不能在Singleton课堂上保护自己。

回答by Satheesh Cheveri

Starting with your given code, "Double-Checked Locking" can be brocken at some enviroment, When run on a system using the Symantec JIT, it doesn't work. In particular, the Symantec JIT compiles

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

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

to the following (note that the Symantec JIT using a handle-based object allocation system).

到以下(注意赛门铁克 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

As you can see, the assignment to singletons[i].reference is performed before the constructor for Singleton is called. This is completely legal under the existing Java memory model, and also legal in C and C++ (since neither of them have a memory model).

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

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

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

Apart from that

除此之外

  1. It can break if the class is Serializable
  2. It can break if its 'Clonable`
  3. You can break by Reflection(I believe)
  4. it can break ff multiple classloaders are loaded the class
  1. 如果类是,它可能会中断 Serializable
  2. 如果它的“可克隆”,它可能会中断
  3. 你可以突破Reflection(我相信)
  4. 它可以打破多个类加载器加载类


*How do you solve rule breakers?

*你如何解决规则破坏者?

  1. It is much safer to do eager initialization
  2. To prevent deserializing to create new object you may override readResolve()method in your class and throw exception
  3. To prevent cloning, you may overrride clone()and throw CloneNotSupportedexception
  4. To escape for reflective instantion, we can add check in the constructor and throw exception.
  1. 进行急切初始化要安全得多
  2. 为了防止反序列化以创建新对象,您可以覆盖readResolve()类中的方法并抛出异常
  3. 为了防止克隆,您可以覆盖clone()并抛出CloneNotSupported异常
  4. 为了逃避反射实例,我们可以在构造函数中添加检查并抛出异常。

Example

例子

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


After all I would suggest to use Enum as the safest way for Singleton (Since java5 the best way to do it is to use an enum)

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

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

回答by Evgeniy Dorofeev

One way is Serialization. If you do not implement readResolve then reading a singleton with ObjectInputStream.readObject() will return a new instance of this singleton.

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

回答by Sean Patrick Floyd

Actually a safe version without the need for synchronization is 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();
  }
}

Other safe versions are:

其他安全版本是:

  • 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;
    }
    
  • 急切初始化

    public final class Singleton{
        public static final Singleton getInstance(){
            return INSTANCE;
        }
        private Singleton();
        private static final Singleton INSTANCE = new Singleton();
    }
    
  • 枚举单例

    public enum Singleton{
        INSTANCE;
    }
    

All of these versions have pros and cons, but none of them needs explicit synchronization since they all rely on the ClassLoader and its built-in Thread safety.

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

As the others have written, you can break some of these patterns through Deserialization. Read Effective Java by Joshua Bloch (Items 74 to 78) about preventing such attacks (the enum singleton pattern is safe against such attacks out of the box).

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

回答by Tim B

The synchronized approach will work, but will also slow down every access to the singleton to protect something that only ever happens in the first access.

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

The simplest and safest way is just to do eager initialization, that is always safe as Java guarantees all member variables are set before it lets anyone access them.

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

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

    private Singleton() {
    }

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

Your current approach is actually broken even with your synchronized loop - because double-check locking is broken. You need to mark the singleton variable as volatileif you are going to use it for double-check locking since otherwise there are still ways for threads to access an incompletely-initialized object. See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Javafor more details.

即使使用同步循环,您当前的方法实际上也被破坏了 - 因为双重检查锁定被破坏了。您需要标记单例变量,就volatile好像您要使用它进行双重检查锁定一样,否则线程仍然可以访问未完全初始化的对象。有关更多详细信息,请参阅http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java

回答by Kuldeep Singh

Suppose you have implemented Singleton class as "SingletonPattern" as below. package com.example.pattern;

假设您已将 Singleton 类实现为“SingletonPattern”,如下所示。包 com.example.pattern;

public class SingletonPattern
{
  private static SingletonPattern pattern;

  public static SingletonPattern getInstance()
  {
    if (pattern == null)
    {
      synchronized (SingletonPattern.class)
      {
        if (pattern == null)
        {
          pattern = new SingletonPattern();
        }
      }
    }
    return pattern;
  }
}

Now you can break you the Singleton behavior of this class by using following approach.

现在,您可以使用以下方法破坏此类的单例行为。

Class c = Class.forName("com.example.pattern.SingletonPattern");
System.out.println((SingltonPattern) c.newInstance());

回答by Deepak

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

** Singleton Test **

** 单例测试 **

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

}
}

回答by Dharma

Class cls = Singleton.class;

类 cls = Singleton.class;

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

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