现在更好的 Java 单例模式?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5822827/
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
The better Java singleton pattern nowadays?
提问by Aliaksandr Kazlou
You know, that since Java 5 is released the recommended way to write Singleton pattern in Java is using enum.
您知道,自从 Java 5 发布以来,在 Java 中编写单例模式的推荐方法是使用枚举。
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:
但是,我不喜欢这样做 - 强制客户端使用 Singleton.INSTANCE 以便访问单例实例。也许,将单例隐藏在普通类中的更好方法,并提供对单例设施的更好访问:
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 )
编辑
@all
首先在 Effective Java,第二版中提到了单例模式的枚举用法:wikipedia:Java Enum Singleton。我完全同意我们应该尽可能减少 Singleton 的使用,但我们不能完全摆脱它们。
在我提供另一个示例之前,让我说,第一个带有 ResourceBundle 的示例只是一个案例,示例本身(和类名称)并非来自实际应用程序。但是,需要说的是,我不知道 ResourceBundle 缓存管理,感谢您提供的信息)
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.
下面,单例模式有两种不同的方法,第一种是使用 Enum 的新方法,第二种是我们大多数人以前使用的标准方法。我试图展示它们之间的显着差异。
Singleton using Enum:
ApplicationSingleton class is:
单例使用枚举:
ApplicationSingleton 类是:
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:
应该提到的是:
- Singleton instance was created only once
- Yes, there are several different instances of ApplicationSingletion, but all of them contain the same Singleton instance
- Registry internal data is the same for all differentApplicationSingleton instance
- 单例实例只创建一次
- 是的,ApplicationSingletion 有几个不同的实例,但它们都包含相同的 Singleton 实例
- 注册表内部数据对于所有不同的ApplicationSingleton 实例都是相同的
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:
单例使用标准方法:
ApplicationSingleton 类是:
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):
Registry 类是(注意 Registry 和 Data 类应该明确地实现 Serializable 以便序列化工作):
//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):
ApplicationSingletionTest 类是(大体相同):
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:
应该提到的是:
- Singleton instance was created several! times
- 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?
因此,似乎 Enum 方法更加可靠和健壮。它是当今在 Java 中使用单例模式的推荐方式吗?你怎么认为?
解释一个有趣的事实:为什么 enum 中的对象可以与其所属的类一起序列化,但没有实现 Serializable?它是功能还是错误?
回答by jprete
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.
我不知道现在枚举是构建单例的 Java 方式。但是如果你打算这样做,你也可以直接使用枚举。我看不出有什么好的理由将单例封装在一堆静态成员方法后面;一旦你这样做了,你也可以开始编写一个带有私有静态成员的静态类。
回答by Anon
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).
相反,使用依赖注入(使用或不使用 Spring 等框架)。
回答by Edwin Dalorzo
[...] the recommended way to write Singleton pattern in Java is using enum [...]
[...] 在 Java 中编写单例模式的推荐方法是使用枚举 [...]
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.
老实说,我不知道这个建议从何而来,但它肯定是有缺陷的。最重要的是因为 Java 中枚举的序列化与普通类的序列化完全不同。
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).
当枚举被序列化时,只有它的名字被写入流中,基本上是因为预期枚举的性质是完全静态的。当枚举被反序列化时,它会基于 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.
似乎您想要完成的是确保 ResourceBundle 的唯一实例,不确定它的两个实例是否会以任何可能的方式影响您的应用程序,但无论如何,JDK 已经实现的 ResourceBundle缓存资源包实例。
The Javadocs say:
Javadocs 说:
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.
我不是该主题的专家,但如果您查看 ResourceBundle Javadocs,也许您可以找到一种更好的方法来处理除此枚举单例之外的资源包。
回答by Sanjay T. Sharma
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.
我发现这种方法的问题是代码重复;如果你的单例有很多方法,你最终会写两次以确保你的委托逻辑有效。查看“按需初始化持有人习惯用语”以替代您的方法,该方法是线程安全的并且不需要 enum hack。
回答by Ahmad Alhaj Hussein
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:
应该提到的是:
- Singleton instance was created several! times
- All registry objects are different objects with its own data
- 单例实例被创建了好几个!次
- 所有注册表对象都是具有自己数据的不同对象
because we force the private constructor to be public in c.setAccessible(true);
因为我们在 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.
值为 true 表示反射对象在使用时应禁止 Java 语言访问检查。值为 false 表示反射对象应强制执行 Java 语言访问检查。
therefore to test the singleton pattern thread-safety you shall use multithreading application
因此要测试单例模式线程安全,您应该使用多线程应用程序
回答by Adriaan Koster
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.
单例的枚举方法由 Joshua Bloch 在他的书Effective Java 中推广。另一个好方法是lazy holder 模式,它有点类似于 OP 的想法。我认为将枚举隐藏在 OP 建议的类中不会增加任何性能或并发风险。
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.
单例仍然被大量使用,尽管它们通常隐藏在我们使用的框架中。是否使用 Singleton 取决于具体情况,我不同意永远不应该使用它们。由于在一些设计不佳的系统中过度使用,单身人士的名声很差。
回答by simpatico
I like enums for a Singleton but the show stopper is when you need inheritance (as I do here). Enums cannot inherit. With dp4ja minimal Singleton looks like that:
我喜欢单例的枚举,但是当你需要继承时(就像我在这里做的那样)。枚举不能继承。使用dp4j,一个最小的单例看起来像这样:
@com.dp4j.Singleton //(lazy=false)
public class MySingleton extends Parent{}
dp4j will actually create this:
dp4j 实际上会创建这个:
@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.comthere's indeed a demonstration on how to unit test the Singleton using the Reflection API.
正如您指出的那样,此解决方案容易受到“反射攻击”的影响。在dp4j.com上确实有关于如何使用反射 API 对单例进行单元测试的演示。