Home  >  Article  >  Java  >  Difficulties in implementing singletons in Java

Difficulties in implementing singletons in Java

伊谢尔伦
伊谢尔伦Original
2016-12-05 11:34:301029browse

There are simple and efficient ways to implement the singleton pattern, but there is no way to ensure the integrity of the singleton under all circumstances.

The singleton pattern means that a class is instantiated only once and is used to represent global or system-wide components. The singleton pattern is often used for logging, factories, window managers, platform component management, etc. I think you should try to avoid using the singleton pattern, because once implemented, it is difficult to change or overload, and it will cause problems such as difficulty in writing test cases and poor code structure. Also, the singleton pattern in the following article is unsafe.

People spend a lot of energy researching how to better implement the singleton pattern, but there is a simple and efficient implementation method. However, there is no one way to ensure the integrity of a singleton under all circumstances. Read below and see if you agree.

Final field

This method privatizes the constructor and provides a public static final object:

public class FooSingleton {    
    public final static FooSingleton INSTANCE = new FooSingleton();   
    private FooSingleton() { }    
    public void bar() { }}

When the class is loaded, the static object is initialized, and at this time the private constructor is used for the first and last time transfer. Even if multiple threads call this class before the class is initialized, the JVM can guarantee that the class is fully initialized when the threads continue to run. However, using reflection and the setAccessible(true) method, it is possible to create other new instances:

Constructor[] constructors = FooSingleton.class.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
FooSingleton spuriousFoo = (FooSingleton) constructor.newInstance(new Object[0]);

We need to modify the constructor so that it does not have to be called multiple times, e.g. throwing an exception when it is called again. Modifying the FooSingleton constructor as follows can prevent such attacks:

public class FooSingleton2 {    
private static boolean INSTANCE_CREATED;    
public final static FooSingleton2 INSTANCE = new FooSingleton2();    
private FooSingleton2() {        
    if (INSTANCE_CREATED) {            
        throw new IllegalStateException("You must only create one instance of this class");        
     } else {            
         INSTANCE_CREATED = true;        
     }    
  }    
      public void bar() { }
}

This seems safer, but in fact it is still just as easy to create a new instance. We just need to modify the INSTANCE_CREATED field and play the same trick again:

Field f = FooSingleton2.class.getDeclaredField("INSTANCE_CREATED");
f.setAccessible(true);
f.set(null, false);
Constructor[] constructors = FooSingleton2.class.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
FooSingleton2 spuriousFoo = (FooSingleton2) constructor.newInstance(new Object[0]);

Any precautions we take may be bypassed, so this solution is not feasible.

Static factory

Using this method, the public members are similar to static factories:

public class FooSingleton3 {    
  public final static FooSingleton3 INSTANCE = new FooSingleton3();    
  private FooSingleton3() { }    
  public static FooSingleton3 getInstance() { return INSTANCE; }    
  public void bar() { }
}

The getInstance() method always returns the same object reference. Although this solution cannot prevent reflections, it still has some advantages. For example, you can change the implementation of a singleton without changing the API. getInstance() appears in almost all singleton implementations, and it also signals that this is really a singleton pattern.

Lazy loading singleton mode

(Translator’s Note: In software engineering, the idiom Initialization-on-demand holder refers to the lazy loading singleton mode, see Wikipedia)

If you want to delay as much as possible To create a singleton (lazy loading), you can use the lazy initialization method to create a singleton thread-safely when the getInstance() method is called for the first time. Compared with the previous solution, which creates a singleton when the class is referenced for the first time (hungry-style loading), this is an improvement. As follows:

public class FooSingleton4 {    
private FooSingleton4() {    
}    
public static FooSingleton4 getInstance() {       
 return FooSingleton4Holder.INSTANCE;    
 }    
private static class FooSingleton4Holder {       
 private static final FooSingleton4 INSTANCE = new FooSingleton4();    
 }
}

Be careful with serialization

If a singleton implements serialization, it faces another threat. Therefore you need to declare all fields as transient (so that it will not be serialized) and provide a custom readResolve() method that returns a reference to the unique instance INSTANCE.

Enumeration

Here we use enumeration as the container of single instance INSTANCE:

public enum FooEnumSingleton {
    INSTANCE;    
    public static FooEnumSingleton getInstance() { 
    return INSTANCE; 
    }    
    public void bar() { }
}

According to Java Language Specification 8.9, "Enum's final cloning method ensures that the enumeration can never be cloned, and its special serialization mechanism ensures that it cannot be deserialized. At the same time, it is also prohibited to use reflection to instantiate the enumeration. This ensures that there will be no other similar enumeration instances outside the enumeration constant. "

We seem to be easily protected against serialization, cloning, and reflection attacks. The first time I saw this passage, I immediately wanted to prove it wrong. As shown in the following code, it is easy to bypass these protections:

Constructor con = FooEnumSingleton.class.getDeclaredConstructors()[0];
 Method[] methods = con.getClass().getDeclaredMethods();
 for (Method method : methods) {
     if (method.getName().equals("acquireConstructorAccessor")) {
         method.setAccessible(true);
         method.invoke(con, new Object[0]);
     }
  }
  Field[] fields = con.getClass().getDeclaredFields();
  Object ca = null;  for (Field field : fields) {
      if (field.getName().equals("constructorAccessor")) {
          field.setAccessible(true);
          ca = field.get(con);
      }
  }  Method method = ca.getClass().getMethod("newInstance", new Class[]{Object[].class});
  method.setAccessible(true);
  FooEnumSingleton spuriousEnum = (FooEnumSingleton) method.invoke(ca, new Object[]{new Object[]{"SPURIOUS_INSTANCE", 1}});
  printInfo(FooEnumSingleton.INSTANCE);
  printInfo(spuriousEnum);
}private static void printInfo(FooEnumSingleton e) {
    System.out.println(e.getClass() + ":" + e.name() + ":" + e.ordinal());
}

Executing this code, you get the result:

class com.blogspot.minborgsjavapot.singleton.FooEnumSingleton:INSTANCE:0
class com.blogspot.minborgsjavapot.singleton.FooEnumSingleton:SPURIOUS_INSTANCE:1

The disadvantage of an enumeration is that it cannot be inherited from another base class because it already inherits from java.lang .Enum. If you want to simulate this kind of inheritance, you can refer to the mixin pattern introduced in my other article.

One advantage of enumerations is that if you later want to have a "dualton" or "tringleton", you only need to add a new enumeration instance. For example, after having a singleton cache, you may also want to introduce multiple layers to the cache.

Conclusion

While bypassing these protections for singletons is not easy, there really is no foolproof solution. If you have a better solution, please feel free to let me know!

Enumeration is a simple and efficient way to implement the singleton pattern. If you want inheritance or lazy loading, lazy initialization is a good choice.

Good luck with your singletons!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn