开发者

The better Java singleton pattern nowadays? [duplicate]

开发者 https://www.devze.com 2023-03-01 14:36 出处:网络
This question already has answers here: What is an efficient way to implement a singleton pattern in Java? [closed]
This question already has answers here: What is an efficient way to implement a singleton pattern in Java? [closed] (29 answers) Closed 6 years ago.

You know, that since Java 5 is released the recommended way to write Singleton pattern in Java is using enum.

public enum Singleton {
    INSTANCE;
}

But, what I don't like in this - is to force the client to use Singleton.INSTANCE in order to have access to the singleton instance. Maybe, the better way to hide Singleton inside the ordinary class, and provide more better access to singleton facilities:

public class ApplicationSingleton {
    private static enum Singleton {
        INSTANCE;               

        private ResourceBundle bundle;

        private Singleton() {
            System.out.println("Singleton instance is created: " + 
            System.开发者_开发百科currentTimeMillis());

            bundle = ResourceBundle.getBundle("application");
        }

        private ResourceBundle getResourceBundle() {
            return bundle;
        }

        private String getResourceAsString(String name) {
            return bundle.getString(name);
        }
    };

    private ApplicationSingleton() {}

    public static ResourceBundle getResourceBundle() {
        return Singleton.INSTANCE.getResourceBundle();
    }

    public static String getResourceAsString(String name) {
        return Singleton.INSTANCE.getResourceAsString(name);
    }
}

So, the client now can simply write:

ApplicationSingleton.getResourceAsString("application.name")

for example. Which is much better then:

Singleton.INSTANCE.getResourceAsString("application.name")

So, the question is: Is it the right way to do it? Does this code have any issues (thread-safety?)? Does it have all the advantages the "enum singleton" pattern has? It seems that it takes the better from the both world. What do you think? Is any better way to achieve this? Thanks.

EDIT

@all

First of all enum usage for Singleton pattern was mentioned in the Effective Java, 2nd Edition: wikipedia:Java Enum Singleton. I totally agree that we should minimize Singleton usage as much as possible, but we can't totally go away from them.

Before I provide another example, let me say, that the first example with ResourceBundle is just a case, the example itself (and the classes names) are not from the real application. But, need to say, that I didn't know about ResourceBundle cache management, thanks for that piece of information )

Below, there are 2 different approaches for Singleton pattern, the first is the new approach with Enum, and the second is the standard approach most of us used before. And I try to show significant differences between them.

Singleton using Enum:

ApplicationSingleton class is:

public class ApplicationSingleton implements Serializable {
    private static enum Singleton {
        INSTANCE;               

        private Registry registry;

        private Singleton() {
            long currentTime = System.currentTimeMillis(); 
            System.out.println("Singleton instance is created: " + 
                    currentTime);

            registry = new Registry(currentTime);
        }

        private Registry getRegistry() {
            return registry;
        }

        private long getInitializedTime() {
            return registry.getInitializedTime();
        }

        private List<Registry.Data> getData() {
            return registry.getData();
        }
    };

    private ApplicationSingleton() {}

    public static Registry getRegistry() {
        return Singleton.INSTANCE.getRegistry();
    }

    public static long getInitializedTime() {
        return Singleton.INSTANCE.getInitializedTime();
    }

    public static List<Registry.Data> getData() {
        return Singleton.INSTANCE.getData();
    }    
}

Registry class is:

public class Registry {
    private List<Data> data = new ArrayList<Data>();
    private long initializedTime;

    public Registry(long initializedTime) {
        this.initializedTime = initializedTime;
        data.add(new Data("hello"));
        data.add(new Data("world"));
    }

    public long getInitializedTime() {
        return initializedTime;
    }

    public List<Data> getData() {
        return data;
    }

    public class Data {      
        private String name;

        public Data(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }                   
    }
}

And test class:

public class ApplicationSingletonTest {     

    public static void main(String[] args) throws Exception {                   

        String rAddress1 = 
            ApplicationSingleton.getRegistry().toString();

        Constructor<ApplicationSingleton> c = 
            ApplicationSingleton.class.getDeclaredConstructor();
        c.setAccessible(true);
        ApplicationSingleton applSingleton1 = c.newInstance();
        String rAddress2 = applSingleton1.getRegistry().toString();

        ApplicationSingleton applSingleton2 = c.newInstance();
        String rAddress3 = applSingleton2.getRegistry().toString();             


        // serialization

        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(applSingleton1);

        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
        ApplicationSingleton applSingleton3 = (ApplicationSingleton) in.readObject();

        String rAddress4 = applSingleton3.getRegistry().toString();

        List<Registry.Data> data = ApplicationSingleton.getData();
        List<Registry.Data> data1 = applSingleton1.getData();
        List<Registry.Data> data2 = applSingleton2.getData();
        List<Registry.Data> data3 = applSingleton3.getData();

        System.out.printf("applSingleton1=%s, applSingleton2=%s, applSingleton3=%s\n", applSingleton1, applSingleton2, applSingleton3);
        System.out.printf("rAddr1=%s, rAddr2=%s, rAddr3=%s, rAddr4=%s\n", rAddress1, rAddress2, rAddress3, rAddress4);
        System.out.printf("dAddr1=%s, dAddr2=%s, dAddr3=%s, dAddr4=%s\n", data, data1, data2, data3);
        System.out.printf("time0=%d, time1=%d, time2=%d, time3=%d\n",
                ApplicationSingleton.getInitializedTime(),
                applSingleton1.getInitializedTime(), 
                applSingleton2.getInitializedTime(),
                applSingleton3.getInitializedTime());
    }

}

And here is the output:

Singleton instance is created: 1304067070250
applSingleton1=ApplicationSingleton@18a7efd, applSingleton2=ApplicationSingleton@e3b895, applSingleton3=ApplicationSingleton@6b7920
rAddr1=Registry@1e5e2c3, rAddr2=Registry@1e5e2c3, rAddr3=Registry@1e5e2c3, rAddr4=Registry@1e5e2c3
dAddr1=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr2=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr3=[Registry$Data@1dd46f7, Registry$Data@5e3974], dAddr4=[Registry$Data@1dd46f7, Registry$Data@5e3974]
time0=1304067070250, time1=1304067070250, time2=1304067070250, time3=1304067070250

What should to mention:

  1. Singleton instance was created only once
  2. Yes, there are several different instances of ApplicationSingletion, but all of them contain the same Singleton instance
  3. Registry internal data is the same for all different ApplicationSingleton instance

So, to summarize: Enum approach works fine and prevent duplicate Singleton creation through reflection attack, and return the same instance after being serialized.

Singleton using standard approach:

ApplicationSingleton class is:

public class ApplicationSingleton implements Serializable {
    private static ApplicationSingleton INSTANCE;

    private Registry registry;

    private ApplicationSingleton() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException ex) {}        
        long currentTime = System.currentTimeMillis();
        System.out.println("Singleton instance is created: " + 
                currentTime);
        registry = new Registry(currentTime);
    }

    public static ApplicationSingleton getInstance() {
        if (INSTANCE == null) {
            return newInstance();
        }
        return INSTANCE;

    }

    private synchronized static ApplicationSingleton newInstance() {
        if (INSTANCE != null) {
            return INSTANCE;
        }
        ApplicationSingleton instance = new ApplicationSingleton();
        INSTANCE = instance;

        return INSTANCE;
    }

    public Registry getRegistry() {
        return registry;
    }

    public long getInitializedTime() {
        return registry.getInitializedTime();
    }

    public List<Registry.Data> getData() {
        return registry.getData();
    }
}

Registry class is (note that Registry and Data classes explicitly should implement Serializable in order serialization to work):

//now Registry should be Serializable in order serialization to work!!!
public class Registry implements Serializable {
    private List<Data> data = new ArrayList<Data>();
    private long initializedTime;

    public Registry(long initializedTime) {
        this.initializedTime = initializedTime;
        data.add(new Data("hello"));
        data.add(new Data("world"));
    }

    public long getInitializedTime() {
        return initializedTime;
    }

    public List<Data> getData() {
        return data;
    }

    // now Data should be Serializable in order serialization to work!!!
    public class Data implements Serializable {      
        private String name;

        public Data(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }                   
    }
}

And ApplicationSingletionTest class is (mostly the same):

public class ApplicationSingletonTest {     

    public static void main(String[] args) throws Exception {

        String rAddress1 = 
            ApplicationSingleton.getInstance().getRegistry().toString();

        Constructor<ApplicationSingleton> c = 
            ApplicationSingleton.class.getDeclaredConstructor();
        c.setAccessible(true);
        ApplicationSingleton applSingleton1 = c.newInstance();
        String rAddress2 = applSingleton1.getRegistry().toString();

        ApplicationSingleton applSingleton2 = c.newInstance();
        String rAddress3 = applSingleton2.getRegistry().toString();             


        // serialization

        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(applSingleton1);

        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
        ApplicationSingleton applSingleton3 = (ApplicationSingleton) in.readObject();

        String rAddress4 = applSingleton3.getRegistry().toString();

        List<Registry.Data> data = ApplicationSingleton.getInstance().getData();
        List<Registry.Data> data1 = applSingleton1.getData();
        List<Registry.Data> data2 = applSingleton2.getData();
        List<Registry.Data> data3 = applSingleton3.getData();

        System.out.printf("applSingleton1=%s, applSingleton2=%s, applSingleton3=%s\n", applSingleton1, applSingleton2, applSingleton3);
        System.out.printf("rAddr1=%s, rAddr2=%s, rAddr3=%s, rAddr4=%s\n", rAddress1, rAddress2, rAddress3, rAddress4);
        System.out.printf("dAddr1=%s, dAddr2=%s, dAddr3=%s, dAddr4=%s\n", data, data1, data2, data3);
        System.out.printf("time0=%d, time1=%d, time2=%d, time3=%d\n",
                ApplicationSingleton.getInstance().getInitializedTime(),
                applSingleton1.getInitializedTime(), 
                applSingleton2.getInitializedTime(),
                applSingleton3.getInitializedTime());
    }

}

And here is the output:

Singleton instance is created: 1304068111203
Singleton instance is created: 1304068111218
Singleton instance is created: 1304068111234
applSingleton1=ApplicationSingleton@16cd7d5, applSingleton2=ApplicationSingleton@15b9e68, applSingleton3=ApplicationSingleton@1fcf0ce
rAddr1=Registry@f72617, rAddr2=Registry@4f1d0d, rAddr3=Registry@1fc4bec, rAddr4=Registry@1174b07
dAddr1=[Registry$Data@1256ea2, Registry$Data@82701e], dAddr2=[Registry$Data@1f934ad, Registry$Data@fd54d6], dAddr3=[Registry$Data@18ee9d6, Registry$Data@19a0c7c], dAddr4=[Registry$Data@a9ae05, Registry$Data@1dff3a2]
time0=1304068111203, time1=1304068111218, time2=1304068111234, time3=1304068111218

What should to mention:

  1. Singleton instance was created several! times
  2. All registry objects are different objects with its own data

So, to summarize: Standard approach is weak for reflection attack, and return different instance after being serialized, but yes with the same data.


So, it seems that Enum approach is more solid and robust. And is it the recommended way for using Singleton pattern in Java nowadays? What do you think?

Interesting fact to explain: why objects inside enum can be serialized with its owning class does not implement Serializable? Is it feature or bug?


I was not aware that enums were the Java way to build singletons these days. But if you're going to do it that way, you may as well just use the enum directly. I don't see any good reason to encapsulate the singleton behind a bunch of static member methods; once you've done that, you may as well have written a static class with private static members to begin with.


The "better" singleton pattern is not to use one.

The approach that you describe, like all approaches that create the singleton via static initialization, is extremely hard to debug.

Instead, use dependency injection (with or without a framework such as Spring).


The problem I see with this approach is code duplication; if your singleton has a lot of methods, you end up writing them twice to make sure your delegation logic works. Look into the "initialization on demand holder idiom" for an alternative to your approach which is thread safe and doesn't need the enum hack.


[...] the recommended way to write Singleton pattern in Java is using enum [...]

Honestly, I do not know where this recommendation comes from, but it is certainly flawed. Above all because serialization of enums in Java is totally different than serialization of an ordinary classes.

When a enum is serialized, only its name is written into the stream, basically because it is expected that the nature of the enum is entirely static. When the enum is deserialized, it is built again based on Enum.valueOf(name).

This implies that if you use a enum as a singleton, and if your singleton is not entirely static, namingly it has dynamic state, then if you serialize it then you are up for some interesting bugs.

This implies that enums cannot always be the solution, although sometimes they could be a good approach.

It seems that what you want to accomplish is to ensure a unique instance of the ResourceBundle, not sure if having two instances of it would affect your application in any possible way, but at any rate, the ResourceBundle as it is implemented by the JDK already caches resource bundle instances.

The Javadocs say:

All resource bundles loaded are cached by default.

This means that if you try to get the same resource bundle twice you get the same instance provided that the cache has not yet been invalidated:

ResourceBundle resource1 = ResourceBundle.getBundle("test");
ResourceBundle resource2 = ResourceBundle.getBundle("test");
assert resource1==resource2;

If you intention is saving some memory, then you do not need a singleton mechanism. The provided cache can do the trick for you.

I am not an expert on the subject, but if you take a look at the ResourceBundle Javadocs perhaps you can find a better way to deal with the resource bundle other than within this enum singlenton.


I need to thank you about this conversation, but I need to update the private constructor code as :

private ApplicationSingleton() {
    long currentTime = System.currentTimeMillis();
    System.out.println("Singleton instance is created: " + currentTime);
}

And here is the output:

Singleton instance is created: 1347981459285
Singleton instance is created: 1347981459285
Singleton instance is created: 1347981459285
applSingleton1=singlton.enums.ApplicationSingleton@12bc8f01,        
applSingleton2=singlton.enums.ApplicationSingleton@3ae34094,   
applSingleton3=singlton.enums.ApplicationSingleton@1da4d2c0

What should to mention:

  1. Singleton instance was created several! times
  2. All registry objects are different objects with its own data

because we force the private constructor to be public in c.setAccessible(true);

A value of true indicates that the reflected object should suppress Java language access checking when it is used. A value of false indicates that the reflected object should enforce Java language access checks.

therefore to test the singleton pattern thread-safety you shall use multithreading application


The enum approach for Singletons was popularized by Joshua Bloch in his book Effective Java. Another good way is the lazy holder pattern, which is sort of similar to OP's idea. I think hiding the enum within a class like OP proposes will not add any performance or concurrency risks.

Singletons are still used a lot, although they are often hidden within the frameworks we use. Whether or not to use a Singleton depends on the situation, I don't agree that they should never ever be used. Singleton has gotten a bad name because of the horrible overuse in some poorly designed systems.


I like enums for a Singleton but the show stopper is when you need inheritance (as I do here). Enums cannot inherit. With dp4j a minimal Singleton looks like that:

@com.dp4j.Singleton //(lazy=false)
public class MySingleton extends Parent{}

dp4j will actually create this:

@Singleton
public class ApplicationSingleton extends Parent{

  @instance
  private static ApplicationSingleton instance = new ApplicationSingleton();

  private ApplicationSingleton(){}

  @getInstance
  public static ApplicationSingleton getInstance(){
     return instance;
  }

As you point out this solution is vulnerable to a 'Reflection attack'. On dp4j.com there's indeed a demonstration on how to unit test the Singleton using the Reflection API.

0

精彩评论

暂无评论...
验证码 换一张
取 消