现在更好的 Java 单例模式?
您知道,自从 Java 5 发布以来,在 Java 中编写单例模式的推荐方法是使用枚举。
public enum Singleton {
INSTANCE;
}
但是,我不喜欢的是强制客户端使用 Singleton.INSTANCE 才能访问单例实例。 也许,更好的方法是将 Singleton 隐藏在普通类中,并提供对 Singleton 设施的更好的访问:
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);
}
}
因此,客户端现在可以简单地编写:
ApplicationSingleton.getResourceAsString("application.name")
例如。 那么,哪个更好:
Singleton.INSTANCE.getResourceAsString("application.name")
所以,问题是:这是正确的方法吗?这段代码有任何问题吗(线程安全?)?它是否具有“枚举单例”模式的所有优点?似乎它从两个世界中获取了更好的东西。你怎么认为?有更好的方法来实现这一目标吗? 谢谢。
编辑
@全部
首先,《Effective Java》第二版中提到了单例模式的枚举用法:wikipedia:Java Enum Singleton。我完全同意我们应该尽可能减少 Singleton 的使用,但我们不能完全摆脱它们。
在提供另一个示例之前,我要说的是,ResourceBundle 的第一个示例只是一个案例,示例本身(以及类名称)并非来自真实的应用程序。但是,需要说的是,我不了解 ResourceBundle 缓存管理,感谢您提供的信息)
下面,单例模式有两种不同的方法,第一个是使用 Enum 的新方法,第二个是使用 Enum 的新方法是我们大多数人之前使用的标准方法。我尝试展示它们之间的显着差异。
使用枚举的单例:
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();
}
}
注册表类是:
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;
}
}
}
测试类:
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());
}
}
这里是输出:
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
值得一提的是:
- Singleton 实例仅创建一次
- 是的,ApplicationSingletion 有多个不同的实例,但它们都包含相同的 Singleton 实例
- 注册表内部对于所有不同 ApplicationSingleton 实例,数据都是相同的
所以,总结一下:枚举方法工作正常,可以防止通过反射攻击重复创建单例,并在序列化后返回相同的实例。
使用标准方法的单例:
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 类是(请注意,Registry 和 Data 类明确应该实现 Serialized 以便序列化工作):
//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;
}
}
}
ApplicationSingletonTest 类是(大部分相同):
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());
}
}
这里是输出:
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
应该提到的是:
- 创建了 Singleton 实例一些! 所有注册表对象
- 都是具有自己数据的不同对象
所以,总结一下:标准方法对于反射攻击来说很弱,并且在序列化后返回不同的实例,但是可以使用相同的数据。
所以,看起来 Enum 方法更加可靠和健壮。这是当今在 Java 中使用 Singleton 模式的推荐方式吗?你觉得怎么样?
需要解释一个有趣的事实:为什么枚举中的对象可以被序列化,而其所属的类没有实现 Serialized?这是功能还是错误?
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:
- 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 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:
- 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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
我不知道枚举是当今构建单例的 Java 方法。但如果你打算这样做,你也可以直接使用枚举。我看不出有什么充分的理由将单例封装在一堆静态成员方法后面;完成此操作后,您可能还编写了一个包含私有静态成员的静态类。
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.
“更好的”单例模式是不使用单例模式。
您描述的方法与通过静态初始化创建单例的所有方法一样,非常难以调试。
相反,使用依赖注入(有或没有 Spring 等框架)。
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.
老实说,我不知道这个建议来自哪里,但它肯定是有缺陷的。最重要的是,Java 中枚举的序列化与普通类的序列化完全不同。
当枚举被序列化时,只有它的名称被写入流中,基本上是因为预期枚举的性质是完全静态的。当枚举被反序列化时,它会基于 Enum.valueOf(name) 再次构建。
这意味着,如果您使用枚举作为单例,并且您的单例不完全静态,即它具有动态状态,那么如果您序列化它,那么您就会遇到一些有趣的错误。
这意味着枚举并不总是解决方案,尽管有时它们可能是一个很好的方法。
看来您想要完成的是确保 ResourceBundle 的唯一实例,不确定拥有两个实例是否会以任何可能的方式影响您的应用程序,但无论如何,ResourceBundle 已经由 JDK 实现了缓存资源包实例。
Javadocs 说:
默认情况下,所有加载的资源包都会被缓存。
这意味着,如果您尝试两次获取相同的资源包,那么只要缓存尚未失效,您就会获得相同的实例:
如果您的目的是节省一些内存,那么您不需要单例机制。提供的缓存可以为您解决问题。
我不是该主题的专家,但如果您查看一下 ResourceBundle Javadocs,也许您可以找到一种更好的方法来处理除此枚举单例之外的资源包。
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:
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.
我需要感谢您的这次对话,但我需要将私有构造函数代码更新为:
这里是输出:
应该提到的是:
,因为我们强制私有构造函数在
c.setAccessible(true);
true 值表示反射对象在使用时应抑制 Java 语言访问检查。 false 值表示反射对象应强制执行 Java 语言访问检查。
因此,要测试单例模式线程安全性,您应该使用多线程应用程序
I need to thank you about this conversation, but I need to update the private constructor code as :
And here is the output:
What should to mention:
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
Joshua Bloch 在他的书 Effective Java 中推广了单例的枚举方法。另一个好方法是惰性持有者模式,这与OP的想法有点相似。我认为将枚举隐藏在像OP建议的类中不会增加任何性能或并发风险。
单例仍然被大量使用,尽管它们通常隐藏在我们使用的框架中。是否使用单例取决于具体情况,我不同意永远不应该使用它们。由于在一些设计不良的系统中可怕的过度使用,Singleton 已经声名狼藉。
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.
我喜欢单例的枚举,但是当你需要继承时就会出现问题(正如我所做的那样此处)。枚举不能继承。
使用 dp4j ,最小的单例看起来像这样:
dp4j 实际上会创建这个:
正如您所指出的,该解决方案容易受到“反射”的影响攻击'。在 dp4j.com 上确实有一个关于如何使用 Reflection API 对 Singleton 进行单元测试的演示。
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:
dp4j will actually create this:
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.