在Java中实现单例设计模式的有效方法是什么?
当前回答
如果需要惰性地加载类的实例变量,则需要双重检查习惯用法。如果需要惰性地加载静态变量或单例,则需要按需初始化持有者习惯用法。
此外,如果单例需要可序列化,则所有其他字段都需要是暂时的,并且需要实现readResolve()方法以保持单例对象不变。否则,每次反序列化对象时,都会创建对象的新实例。readResolve()所做的是用readObject()替换读取的新对象,因为没有变量引用该对象,所以强制对该新对象进行垃圾回收。
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // Original singleton instance.
}
其他回答
我可以说是枚举单例。
在Java中使用枚举通常是声明枚举单例的一种方法。枚举单例可以包含实例变量和实例方法。为了简单起见,还要注意,如果您正在使用任何实例方法,那么如果该方法影响对象的状态,则需要确保该方法的线程安全。
枚举的使用非常容易实现,并且在可序列化对象方面没有缺点,必须通过其他方式规避这些缺点。
/**
* Singleton pattern example using a Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
// Perform operation here
}
}
您可以通过Singleton.INSTANCE访问它,它比在Singleton上调用getInstance()方法简单得多。
1.12枚举常量的序列化枚举常量的序列化方式不同于普通的可序列化或可外部化对象。枚举常量的序列化形式仅由其名称组成;表单中不存在常量的字段值。若要序列化枚举常量,ObjectOutputStream将写入枚举常量的name方法返回的值。要反序列化枚举常量,ObjectInputStream从流中读取常量名称;然后通过调用java.lang.Enum.valueOf方法获得反序列化的常量,并将常量的枚举类型与接收到的常量名称作为参数一起传递。与其他可序列化或可外部化对象一样,枚举常量可以用作后续在序列化流中出现的反向引用的目标。无法自定义枚举常量的序列化过程:在序列化和反序列化期间,将忽略枚举类型定义的任何类特定的writeObject、readObject、readObjectNoData、writeReplace和readResolve方法。类似地,任何serialPersistentFields或serialVersionUID字段声明也会被忽略——所有枚举类型都具有固定的serialVersionID 0L。记录枚举类型的可序列化字段和数据是不必要的,因为发送的数据类型没有变化。引自Oracle文档
传统singleton的另一个问题是,一旦实现了Serializable接口,它们就不再是单一的,因为readObject()方法总是返回一个新的实例,就像Java中的构造函数一样。这可以通过使用readResolve()并丢弃新创建的实例来避免,方法是将其替换为单例,如下所示:
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
如果您的单例类保持状态,这可能会变得更加复杂,因为您需要使它们成为瞬态的,但是在枚举单例中,JVM保证了序列化。
好的阅读
Singleton模式枚举、Singleton和反序列化双重检查锁定和Singleton模式
我对一些答案感到困惑,这些答案建议依赖注入(DI)作为使用单态的替代方案;这些都是不相关的概念。您可以使用DI注入单例或非单例(例如,每个线程)实例。至少如果您使用Spring2.x,这是正确的,我不能为其他DI框架说话。
所以我对OP的回答是(除了最简单的示例代码之外):
使用类似Spring framework的DI框架,然后将其作为DI配置的一部分,无论依赖项是单体的、请求范围的、会话范围的还是其他。
这种方法为您提供了一个很好的解耦(因此是灵活和可测试的)架构,其中是否使用单例是一个容易可逆的实现细节(当然,前提是您使用的任何单例都是线程安全的)。
有时简单的“static Foo=new Foo();”是不够的。想想你想做的一些基本数据插入。
另一方面,您必须同步任何实例化单例变量的方法。同步本身并不坏,但它可能会导致性能问题或锁定(在非常罕见的情况下,使用本示例。解决方案是
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
现在会发生什么?类通过类加载器加载。在从字节数组解释类之后,VM立即执行静态{}-块。这就是全部秘密:静态块只被调用一次,即这个单类加载器加载给定包的给定类(名称)的时间。
维基百科有一些单态的例子,也在Java中。Java5实现看起来非常完整,并且是线程安全的(应用了双重检查锁定)。
如果需要惰性地加载类的实例变量,则需要双重检查习惯用法。如果需要惰性地加载静态变量或单例,则需要按需初始化持有者习惯用法。
此外,如果单例需要可序列化,则所有其他字段都需要是暂时的,并且需要实现readResolve()方法以保持单例对象不变。否则,每次反序列化对象时,都会创建对象的新实例。readResolve()所做的是用readObject()替换读取的新对象,因为没有变量引用该对象,所以强制对该新对象进行垃圾回收。
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // Original singleton instance.
}
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- 将JSON字符串转换为HashMap