在Java中实现单例模式的有效方法是什么?
使用枚举:
public enum Foo { INSTANCE; }
Joshua Bloch 在Google I/O 2008的Effective Java Reloaded演讲中解释了这种方法:链接到视频.另见他演示文稿的幻灯片30-32(effective_java_reloaded.pdf):
实现可序列化单例的正确方法
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
编辑:"Effective Java"的在线部分说:
"这种方法在功能上等同于公共领域方法,除了它更简洁,免费提供序列化机制,并提供防止多个实例化的铁定保证,即使面对复杂的序列化或反射攻击.虽然这种方法有尚未被广泛采用,单元素枚举类型是实现单例的最佳方式."
根据用途,有几个"正确"的答案.
从java5开始,最好的方法是使用枚举:
public enum Foo { INSTANCE; }
前java5,最简单的情况是:
public final class Foo { private static final Foo INSTANCE = new Foo(); private Foo() { if (INSTANCE != null) { throw new IllegalStateException("Already instantiated"); } } public static Foo getInstance() { return INSTANCE; } public Object clone() throws CloneNotSupportedException{ throw new CloneNotSupportedException("Cannot clone instance of this class"); } }
我们来看看代码吧.首先,你希望课程是最终的.在这种情况下,我使用了final
关键字让用户知道它是最终的.然后,您需要将构造函数设置为私有,以防止用户创建自己的Foo.从构造函数中抛出异常会阻止用户使用反射来创建第二个Foo.然后创建一个private static final Foo
字段来保存唯一的实例,并创建一个public static Foo getInstance()
返回它的方法.Java规范确保仅在首次使用类时调用构造函数.
当你有一个非常大的对象或繁重的构造代码并且还有其他可访问的静态方法或字段可能在需要实例之前使用时,那么你只需要使用延迟初始化.
您可以使用a private static class
来加载实例.然后代码看起来像:
public final class Foo { private static class FooLoader { private static final Foo INSTANCE = new Foo(); } private Foo() { if (FooLoader.INSTANCE != null) { throw new IllegalStateException("Already instantiated"); } } public static Foo getInstance() { return FooLoader.INSTANCE; } }
由于该行private static final Foo INSTANCE = new Foo();
仅在实际使用类FooLoader时执行,因此它负责延迟实例化,并且保证是线程安全的.
当您还希望能够序列化对象时,需要确保反序列化不会创建副本.
public final class Foo implements Serializable { private static final long serialVersionUID = 1L; private static class FooLoader { private static final Foo INSTANCE = new Foo(); } private Foo() { if (FooLoader.INSTANCE != null) { throw new IllegalStateException("Already instantiated"); } } public static Foo getInstance() { return FooLoader.INSTANCE; } @SuppressWarnings("unused") private Foo readResolve() { return FooLoader.INSTANCE; } }
该方法readResolve()
将确保将返回唯一的实例,即使该对象在上一次运行的程序中被序列化也是如此.
免责声明:我刚刚总结了所有令人敬畏的答案,并用我的话写下来.
在实施Singleton时,我们有2个选项
1.延迟加载
2.早期加载
延迟加载会增加一些开销(很多是诚实的),所以只有当你有一个非常大的对象或繁重的构造代码并且还有其他可访问的静态方法或字段可能在需要实例之前使用时才使用它,然后才会你需要使用延迟初始化.否则选择早期加载是一个不错的选择.
实现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; } }
这被称为"双重锁定成语".很容易忘记易变的陈述,很难理解为什么有必要.
有关详细信息:http: //www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
现在我们确定邪恶的线索,但残酷的序列化怎么样?我们必须确保即使在de-serialiaztion中也没有创建新对象
class Foo implements Serializable { private static final long serialVersionUID = 1L; private static volatile Foo INSTANCE = null; // 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(); }
这就是不再担心序列化,线程和丑陋的代码.此外枚举单被延迟初始化.
这种方法在功能上等同于公共字段方法,除了它更简洁,免费提供序列化机制,并提供防止多实例化的铁定保证,即使面对复杂的序列化或反射攻击.虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例的最佳方法.
-Joshua Bloch in"Effective Java"
现在您可能已经意识到为什么ENUMS被认为是实施Singleton的最佳方式,感谢您的耐心:)
在我的博客上更新了它.
Stu Thompson发布的解决方案在Java5.0及更高版本中有效.但我宁愿不使用它因为我认为它容易出错.
很容易忘记易变的陈述,很难理解为什么有必要.没有volatile,由于双重检查锁定反模式,此代码将不再是线程安全的.请参阅Java Concurrency in Practice第16.2.4节中的更多相关内容.简而言之:这种模式(在Java5.0之前或没有volatile语句之前)可以返回对(仍然)处于错误状态的Bar对象的引用.
这种模式是为了性能优化而发明的.但这真的不再是一个真正的问题了.以下延迟初始化代码快速且更重要 - 更易于阅读.
class Bar { private static class BarHolder { public static Bar bar = new Bar(); } public static Bar getBar() { return BarHolder.bar; } }
Java 5+中的线程安全:
class Foo { private static volatile Bar bar = null; public static Bar getBar() { if (bar == null) { synchronized(Foo.class) { if (bar == null) bar = new Bar(); } } return bar; } }
编辑:注意volatile
这里的修饰符.:)这很重要,因为没有它,JMM(Java内存模型)无法保证其他线程看到其值的更改.同步不会解决这个问题 - 它只序列化对该代码块的访问.
编辑2:@Bno的回答详细介绍了Bill Pugh(FindBugs)推荐的方法,并且可以说是更好的.去阅读并投票他的答案.
忘记延迟初始化,这太有问题了.这是最简单的解决方案:
public class A { private static final A INSTANCE = new A(); private A() {} public static A getInstance() { return INSTANCE; } }
确保你真的需要它.做谷歌的"单身反模式"看一些反对它的论点.我想它没有什么本质上的错误,但它只是一种暴露一些全球资源/数据的机制,所以要确保这是最好的方法.特别是我发现依赖注入更有用,特别是如果你也使用单元测试,因为DI允许你使用模拟资源进行测试.
我对一些答案感到困惑,这些答案表明DI可以替代使用单身人士; 这些是不相关的概念.您可以使用DI注入单例或非单例(例如每线程)实例.如果你使用Spring 2.x,至少会这样,我不能代表其他DI框架.
所以我对OP的回答是(除了最简单的示例代码之外的所有代码):
然后使用像Spring这样的DI框架
使其成为DI配置的一部分,无论您的依赖项是单例,请求作用域,会话作用域还是其他.
这种方法为您提供了一个很好的解耦(因此也是灵活且可测试的)架构,其中是否使用单例是一个易于反转的实现细节(假设您使用的任何单例都是线程安全的).
在写之前真的要考虑为什么你需要一个单身人士.关于使用它们存在准宗教的争论,如果你使用Java中的google singletons,你很容易就会发现它们.
我个人试图尽可能多地避免单身,原因很多,其中大部分都可以通过谷歌搜索单身人士来找到.我觉得单身人士经常被滥用,因为他们很容易被所有人理解,他们被用作将"全局"数据转化为OO设计的机制,因为它很容易绕过对象生命周期管理(或者真的在考虑如何从B内部做一个A).查看控制反转(IoC)或依赖注入(DI)等内容,以获得良好的中间地带.
如果你真的需要一个,那么维基百科有一个很好的例子来正确实现单例.
以下是3种不同的方法
1)Enum
/** * Singleton pattern example using Java Enumj */ public enum EasySingleton{ INSTANCE; }
2)双重检查锁定/延迟加载
/** * Singleton pattern example with Double checked Locking */ public class DoubleCheckedLockingSingleton{ private static volatile DoubleCheckedLockingSingleton INSTANCE; private DoubleCheckedLockingSingleton(){} public static DoubleCheckedLockingSingleton getInstance(){ if(INSTANCE == null){ synchronized(DoubleCheckedLockingSingleton.class){ //double checking Singleton instance if(INSTANCE == null){ INSTANCE = new DoubleCheckedLockingSingleton(); } } } return INSTANCE; } }
3)静态工厂方法
/** * Singleton pattern example with static factory method */ public class Singleton{ //initailzed during class loading private static final Singleton INSTANCE = new Singleton(); //to prevent creating another instance of Singleton private Singleton(){} public static Singleton getSingleton(){ return INSTANCE; } }
我使用Spring Framework来管理我的单身人士.它不强制实现类的"单例"(如果涉及多个类加载器,则无论如何都无法实现),但它提供了一种非常简单的方法来构建和配置不同的工厂来创建不同类型的对象.
版本1:
public class MySingleton { private static MySingleton instance = null; private MySingleton() {} public static synchronized MySingleton getInstance() { if(instance == null) { instance = new MySingleton(); } return instance; } }
延迟加载,线程安全,阻塞,低性能因为synchronized
.
版本2:
public class MySingleton { private MySingleton() {} private static class MySingletonHolder { public final static MySingleton instance = new MySingleton(); } public static MySingleton getInstance() { return MySingletonHolder.instance; } }
延迟加载,线程安全,无阻塞,高性能.
维基百科有一些单身人士的例子,也有Java.Java 5实现看起来非常完整,并且是线程安全的(应用了双重检查锁定).
如果您不需要延迟加载,那么只需尝试
public class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return Singleton.INSTANCE; } protected Object clone() { throw new CloneNotSupportedException(); } }
如果您想要延迟加载并且希望Singleton是线程安全的,请尝试使用双重检查模式
public class Singleton { private static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if(null == instance) { synchronized(Singleton.class) { if(null == instance) { instance = new Singleton(); } } } return instance; } protected Object clone() { throw new CloneNotSupportedException(); } }
由于双重检查模式不能保证工作(由于编译器的一些问题,我不知道更多.),您还可以尝试同步整个getInstance方法或为您的所有单身人士创建一个注册表.
我会说Enum singleton
在Java中使用枚举的单例通常是声明枚举单例的方法.枚举单例可以包含实例变量和实例方法.为简单起见,还要注意,如果您使用任何实例方法而不是您需要确保该方法的线程安全性,如果它完全影响对象的状态.
枚举的使用非常容易实现,并且对于可序列化对象没有缺点,这些对象必须以其他方式规避.
/** * Singleton pattern example using Java Enum */ public enum Singleton { INSTANCE; public void execute (String arg) { //perform operation here } }
您可以通过Singleton.INSTANCE
比getInstance()
在Singleton上调用方法更容易访问它.
1.12枚举常量的序列化
枚举常量的序列化与普通的可序列化或可外部化的对象不同.枚举常量的序列化形式仅由其名称组成; 常量的字段值不存在于表单中.要序列化枚举常量,请
ObjectOutputStream
写入枚举常量名称方法返回的值.要反序列化枚举常量,请ObjectInputStream
从流中读取常量名称; 然后通过调用java.lang.Enum.valueOf
方法获得反序列化常量,将常量的枚举类型与接收到的常量名称一起作为参数传递.与其他可序列化或可外部化的对象一样,枚举常量可以作为随后出现在序列化流中的反向引用的目标.通过枚举常数被序列不能被定制的方法:任何类特定的
writeObject
,readObject
,readObjectNoData
,writeReplace
,和readResolve
由枚举类型定义的方法的序列化和反序列化期间被忽略.类似地,任何serialPersistentFields
或serialVersionUID
字段声明也被忽略 - 所有枚举类型都有固定serialVersionUID
的0L
.记录枚举类型的可序列化字段和数据是不必要的,因为发送的数据类型没有变化.引自Oracle文档
传统单例的另一个问题是,一旦实现了Serializable
接口,它们就不再是Singleton,因为readObject()
方法总是在Java中返回一个像构造函数这样的新实例.这可以通过使用readResolve()
如下替换单例来使用和丢弃新创建的实例来避免
// readResolve to prevent another instance of Singleton private Object readResolve(){ return INSTANCE; }
如果您的Singleton类维护状态,这可能变得更加复杂,因为您需要使它们成为瞬态,但在Enum Singleton中,JVM保证了序列化.
好读
单身模式
枚举,单身和反序列化
双重检查锁定和Singleton模式
在这个问题上可能会有点晚了,但是在实施单身人士方面存在很多细微差别.在许多情况下不能使用支架图案.和IMO在使用volatile时 - 你也应该使用局部变量.让我们从头开始并迭代问题.你会明白我的意思.
第一次尝试可能看起来像这样:
public class MySingleton { private static MySingleton INSTANCE; public static MySingleton getInstance() { if (INSTANCE == null) { INSTANCE = new MySingleton(); } return INSTANCE; } ... }
这里我们有MySingleton类,它有一个名为INSTANCE的私有静态成员,以及一个名为getInstance()的公共静态方法.第一次调用getInstance()时,INSTANCE成员为null.然后,流将进入创建条件并创建MySingleton类的新实例.对getInstance()的后续调用将发现已经设置了INSTANCE变量,因此不会创建另一个MySingleton实例.这确保了只有一个MySingleton实例在getInstance()的所有调用者之间共享.
但是这个实现有一个问题.多线程应用程序将在创建单个实例时具有竞争条件.如果多个执行线程同时(或大约)同时命中getInstance()方法,它们将各自看到INSTANCE成员为null.这将导致每个线程创建一个新的MySingleton实例,然后设置INSTANCE成员.
private static MySingleton INSTANCE; public static synchronized MySingleton getInstance() { if (INSTANCE == null) { INSTANCE = new MySingleton(); } return INSTANCE; }
这里我们使用方法签名中的synchronized关键字来同步getInstance()方法.这肯定会解决我们的竞争状况.线程现在将阻止并一次输入一个方法.但它也会产生性能问题.此实现不仅同步单个实例的创建,还将所有调用同步到getInstance(),包括读取.读取不需要同步,因为它们只返回INSTANCE的值.由于读取将构成我们调用的大部分(请记住,实例化仅在第一次调用时发生),我们将通过同步整个方法而导致不必要的性能损失.
private static MySingleton INSTANCE; public static MySingleton getInstance() { if (INSTANCE == null) { synchronize(MySingleton.class) { INSTANCE = new MySingleton(); } } return INSTANCE; }
在这里,我们将同步从方法签名移动到包装MySingleton实例创建的同步块.但这能解决我们的问题吗?好吧,我们不再阻止阅读,但我们也向前退了一步.多个线程将同时或大约同时命中getInstance()方法,并且它们都将INSTANCE成员视为null.然后,他们将点击同步块,其中一个将获得锁并创建实例.当该线程退出该块时,其他线程将争用该锁,并且每个线程将逐个通过该块并创建该类的新实例.所以我们回到了我们开始的地方.
private static MySingleton INSTANCE; public static MySingleton getInstance() { if (INSTANCE == null) { synchronized(MySingleton.class) { if (INSTANCE == null) { INSTANCE = createInstance(); } } } return INSTANCE; }
在这里,我们从INSIDE块发出另一张支票.如果已经设置了INSTANCE成员,我们将跳过初始化.这称为双重检查锁定.
这解决了我们多实例化的问题.但是,我们的解决方案又一次提出了另一项挑战.其他线程可能不会"看到"INSTANCE成员已更新.这是因为Java优化了内存操作.线程将变量的原始值从主存储器复制到CPU的缓存中.然后,将对值的更改写入该缓存并从中读取.这是Java的一项功能,旨在优化性能.但这给我们的单例实现带来了问题.第二个线程 - 由不同的CPU或核心使用不同的缓存处理 - 将不会看到第一个线程所做的更改.这将导致第二个线程将INSTANCE成员视为null,从而强制创建我们的单例的新实例.
private static volatile MySingleton INSTANCE; public static MySingleton getInstance() { if (INSTANCE == null) { synchronized(MySingleton.class) { if (INSTANCE == null) { INSTANCE = createInstance(); } } } return INSTANCE; }
我们通过在INSTANCE成员的声明中使用volatile关键字来解决这个问题.这将告诉编译器始终读取和写入主内存,而不是CPU缓存.
但这种简单的改变需要付出代价.因为我们绕过CPU缓存,所以每次操作易失性INSTANCE成员时我们都会受到性能影响 - 我们会这样做4次.我们仔细检查存在(1和2),设置值(3),然后返回值(4).有人可能认为这条路径是边缘情况,因为我们只在第一次调用方法时创建实例.也许创作的表现可以容忍.但即使是我们的主要用例read也会对volatile组件进行两次操作.一旦检查存在,再次返回其值.
private static volatile MySingleton INSTANCE; public static MySingleton getInstance() { MySingleton result = INSTANCE; if (result == null) { synchronized(MySingleton.class) { result = INSTANCE; if (result == null) { INSTANCE = result = createInstance(); } } } return result; }
由于性能影响是由于直接在volatile成员上运行,因此让我们将局部变量设置为volatile的值,而不是对局部变量进行操作.这将减少我们对易失性操作的次数,从而回收我们失去的一些性能.请注意,当我们进入synchronized块时,我们必须再次设置本地变量.这可确保它在我们等待锁定时发生的任何更改都是最新的.
我最近写了一篇关于此的文章.解构单身人士.您可以在这些示例中找到更多信息,并在那里找到"持有者"模式的示例.还有一个真实的例子展示了双重检查的volatile方法.希望这可以帮助.
There are 4 ways to create a singleton in java. 1- eager initialization singleton public class Test{ private static final Test test = new Test(); private Test(){} public static Test getTest(){ return test; } ? 2- lazy initialization singleton (thread safe) public class Test { private static volatile Test test; private Test(){} public static Test getTest() { if(test == null) { synchronized(Test.class) { if(test == null){test = new Test(); } } } return test; ? 3- Bill Pugh Singleton with Holder Pattern (Preferably the best one) public class Test { private Test(){} private static class TestHolder{ private static final Test test = new Test(); } public static Test getInstance(){ return TestHolder.test; } } 4- enum singleton public enum MySingleton { INSTANCE; private MySingleton() { System.out.println("Here"); } }