【发布时间】:2011-08-14 22:27:06
【问题描述】:
你知道,自从 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,第 2 版中提到了 Singleton 模式的枚举用法:wikipedia:Java Enum Singleton。我完全同意我们应该尽可能减少 Singleton 的使用,但我们不能完全放弃它们。
在我提供另一个示例之前,让我说,ResourceBundle 的第一个示例只是一个案例,示例本身(和类名)并非来自真实应用程序。但是,需要说明的是,我不知道 ResourceBundle 缓存管理,感谢您提供的信息)
下面,Singleton 模式有两种不同的方法,第一种是 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
应该说什么:
- 单例实例只创建一次
- 是的,ApplicationSingletion 有几个不同的实例,但它们都包含相同的 Singleton 实例
- 所有不同ApplicationSingleton实例的注册表内部数据都是相同的
所以,总结一下:Enum 方法工作正常,可以防止通过反射攻击创建重复的 Singleton,并在序列化后返回相同的实例。
使用标准方法的单例:
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 类应显式实现 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;
}
}
}
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());
}
}
这是输出:
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
应该说什么:
- 已创建多个单例实例!次
- 所有注册表对象都是具有自己数据的不同对象
所以,总结一下:标准方法对于反射攻击是弱的,并且在被序列化后返回不同的实例,但是对于相同的数据是可以的。
因此,Enum 方法似乎更加可靠和稳健。它是当今在 Java 中使用单例模式的推荐方式吗?你怎么看?
有趣的事实要解释:为什么枚举中的对象可以使用其拥有的类进行序列化,但没有实现 Serializable?是功能还是错误?
【问题讨论】:
-
最好的单身人士是不存在的单身人士:)
-
同意 Bozho,尤其是在这种情况下使用
ResourceBundle对象。ResourceBundle已经缓存了以前检索到的包,因此调用getBundle("bundle.name")将检索现有的。然后像往常一样使用该对象 -
ResourceBundles 为了您的方便已经被缓存了,也许您可以避免所有的单例问题并相信已经在其中实现的缓存机制。
-
您应该减少问题中的详细说明。您的问题现在比问题更多。只需说明推荐的来源以及推荐原因即可。
标签: java design-patterns enums singleton