在Java中实现单例设计模式的有效方法是什么?
当前回答
忘记延迟初始化;这太有问题了。这是最简单的解决方案:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
其他回答
有时简单的“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中使用枚举通常是声明枚举单例的一种方法。枚举单例可以包含实例变量和实例方法。为了简单起见,还要注意,如果您正在使用任何实例方法,那么如果该方法影响对象的状态,则需要确保该方法的线程安全。
枚举的使用非常容易实现,并且在可序列化对象方面没有缺点,必须通过其他方式规避这些缺点。
/**
* 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模式
免责声明:我刚刚总结了所有很棒的答案,并用自己的话写了出来。
在实施Singleton时,我们有两种选择:
延迟加载早期加载
延迟加载增加了位开销(老实说,很多),所以只有当您有非常大的对象或大量的构造代码,并且有其他可访问的静态方法或字段(可能在需要实例之前使用)时,才需要使用延迟初始化。否则,选择提前加载是一个不错的选择。
实现单例的最简单方法是:
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
一切都很好,除了它是一个早期加载的单例。让我们尝试延迟加载的单例
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
到目前为止还不错,但我们的英雄在与多个邪恶线程单独战斗时将无法生存,这些线程需要我们英雄的许多实例。因此,让我们保护它免受邪恶的多线程攻击:
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
但这还不足以保护英雄,真的!!!这是我们能/应该做的最好的事情来帮助我们的英雄:
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
这被称为“双重检查锁定习惯用法”。人们很容易忘记不稳定的说法,也很难理解为什么有必要这样做。详细信息:“双重检查锁定失败”声明
现在我们确定了邪恶的线索,但残酷的连载呢?我们必须确保即使在反序列化时也不会创建新对象:
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// The rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
方法readResolve()将确保返回唯一的实例,即使对象在我们的程序的上一次运行中被序列化。
最后,我们添加了足够的线程和序列化保护,但我们的代码看起来又大又难看。让我们给我们的英雄一个新形象:
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
是的,这是我们的英雄:)
由于行private static final Foo INSTANCE=new Foo();仅在实际使用FooLoader类时执行,这会处理延迟实例化,并保证线程安全。
我们已经走到了今天。以下是实现我们所做一切的最佳方式:
public enum Foo {
INSTANCE;
}
内部将被视为
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
就是这样!不再担心序列化、线程和丑陋的代码。ENUMS单例也被延迟初始化。
该方法在功能上等同于公共领域方法,除了它更简洁,提供了序列化机制免费提供,并提供多重保险实例化,即使面对复杂的序列化或反射攻击。虽然这种方法尚未被广泛采用,单个元素枚举类型是实现单例的最佳方式。
-《有效的Java》中的约书亚·布洛赫
现在您可能已经意识到为什么ENUMS被认为是实现单例的最佳方式,并感谢您的耐心:)
在我的博客上更新了它。
忘记延迟初始化;这太有问题了。这是最简单的解决方案:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
这是如何实现一个简单的单例:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
这是如何正确地延迟创建单例:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
推荐文章
- 在流中使用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