在 Java 中实现单例设计模式的有效方法是什么?
29 回答
使用枚举:
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”的在线部分说:
“这种方法在功能上等同于公共字段方法,只是它更简洁,免费提供序列化机制,并且即使面对复杂的序列化或反射攻击,也能提供防止多次实例化的铁定保证。虽然这种方法已经尚未被广泛采用,单元素枚举类型是实现单例的最佳方式。”
根据使用情况,有几个“正确”的答案。
从 Java 5 开始,最好的方法是使用枚举:
public enum Foo {
INSTANCE;
}
在 Java 5 之前,最简单的情况是:
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 规范确保仅在第一次使用该类时才调用构造函数。
当您有一个非常大的对象或繁重的构造代码,并且还有其他可访问的静态方法或字段可能在需要实例之前使用时,那么并且只有这样您才需要使用延迟初始化。
您可以使用 aprivate 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 时,我们有两个选择:
- 延迟加载
- 早期加载
延迟加载会增加一点开销(老实说很多),所以只有当你有一个非常大的对象或繁重的构造代码并且还有其他可访问的静态方法或字段可能在需要实例之前使用时才使用它,然后且仅那么你需要使用延迟初始化。否则,选择提前加载是一个不错的选择。
实现单例最简单的方法是:
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;
}
}
这被称为“双重检查锁定习语”。很容易忘记 volatile 语句,很难理解为什么它是必要的。详情:“双重检查锁被破坏”声明
现在我们确定了邪恶的线程,但是残酷的连载呢?我们必须确保即使在反序列化时也不会创建新对象:
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单例也被延迟初始化。
这种方法在功能上等同于公共字段方法,只是它更简洁,免费提供序列化机制,并提供针对多次实例化的铁定保证,即使面对复杂的序列化或反射攻击。虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例的最佳方式。
——Joshua Bloch 在“有效的 Java”中
现在您可能已经意识到为什么 ENUMS 被认为是实现单例的最佳方式,感谢您的耐心等待 :)
在我的博客上更新了它。
Stu Thompson 发布的解决方案在 Java 5.0 及更高版本中有效。但我宁愿不使用它,因为我认为它容易出错。
很容易忘记 volatile 语句,很难理解为什么它是必要的。由于双重检查锁定反模式,如果没有 volatile,此代码将不再是线程安全的。在Java Concurrency in Practice的第 16.2.4 段中了解更多信息。简而言之:这种模式(在 Java 5.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 内存模型)不能保证其他线程看到其值的变化。同步并不关心这一点——它只序列化对该代码块的访问。
@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)作为使用单例的替代方法的答案感到困惑;这些是不相关的概念。您可以使用 DI 注入单例或非单例(例如,每线程)实例。如果您使用 Spring 2.x,至少这是真的,我不能代表其他 DI 框架。
所以我对 OP 的回答是(除了最简单的示例代码之外):
- 使用像Spring Framework这样的 DI 框架,然后
- 无论您的依赖项是单例、请求范围、会话范围或其他什么,都将其作为您的 DI 配置的一部分。
这种方法为您提供了一个很好的解耦(因此灵活且可测试)架构,其中是否使用单例是一个容易可逆的实现细节(当然,前提是您使用的任何单例都是线程安全的)。
真正考虑一下为什么在编写之前需要一个单例。有一个关于使用它们的准宗教辩论,如果你用谷歌搜索 Java 中的单例,你很容易偶然发现。
就个人而言,出于多种原因,我尽量避免使用单例,其中大部分可以通过谷歌搜索单例找到。我觉得单身人士经常被滥用,因为他们很容易被每个人理解。它们被用作将“全局”数据引入 OO 设计的机制,并且它们被使用是因为它很容易绕过对象生命周期管理(或者真正考虑如何从 B 内部执行 A)。查看控制反转(IoC) 或依赖注入(DI) 之类的东西,以获得一个不错的中间立场。
如果你真的需要一个,那么维基百科有一个很好的例子来说明单例的正确实现。
以下是三种不同的方法
枚举
/** * Singleton pattern example using Java Enum */ public enum EasySingleton { INSTANCE; }
双重检查锁定/延迟加载
/** * 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; } }
静态工厂方法
/** * Singleton pattern example with static factory method */ public class Singleton { // Initialized 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来管理我的单例。
它不强制执行类的“单一性”(如果涉及多个类加载器,您无论如何都不能真正做到这一点),但它提供了一种非常简单的方法来构建和配置不同的工厂以创建不同类型的对象。
实现单例有很多细微差别。持有人模式不能在许多情况下使用。并且 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成员为空。然后流程将落入创建条件并创建 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;
}
在这里,我们从块内部发出另一张支票。如果已经设置了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 缓存,所以每次对 volatile INSTANCE成员进行操作时都会对性能造成影响——我们做了四次。我们再次检查存在(1 和 2),设置值(3),然后返回值(4)。有人可能会说这条路径是边缘情况,因为我们只在第一次调用该方法时创建实例。也许对创作的影响是可以容忍的。但即使是我们的主要用例 reads 也会对 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 的值,并改为对局部变量进行操作。这将减少我们对 volatile 进行操作的次数,从而收回我们失去的一些性能。请注意,当我们进入同步块时,我们必须再次设置我们的局部变量。这可以确保它在我们等待锁定时发生的任何更改都是最新的。
我最近写了一篇关于这个的文章。解构单身人士。您可以在此处找到有关这些示例的更多信息以及“持有人”模式的示例。还有一个真实的例子展示了双重检查的易失性方法。
维基百科有一些单例的例子,也用 Java。Java 5 实现看起来相当完整,并且是线程安全的(应用了双重检查锁定)。
版本 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;
}
}
延迟加载,线程安全,非阻塞,高性能。
如果您不需要延迟加载,那么只需尝试:
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();
}
}
如果您想要延迟加载并且希望您的单例是线程安全的,请尝试双重检查模式:
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 方法或为所有单例创建注册表。
我会说一个枚举单例。
在 Java 中使用枚举的单例通常是声明枚举单例的一种方式。枚举单例可能包含实例变量和实例方法。为简单起见,还请注意,如果您使用任何实例方法,那么如果它影响对象的状态,则需要确保该方法的线程安全。
枚举的使用非常容易实现,并且没有关于可序列化对象的缺点,这些缺点必须以其他方式规避。
/**
* Singleton pattern example using a Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
// Perform operation here
}
}
可以通过 来访问,比在 Singleton 上Singleton.INSTANCE
调用方法要容易得多。getInstance()
1.12 枚举常量的序列化
枚举常量的序列化方式与普通的可序列化或可外部化对象不同。枚举常量的序列化形式仅由其名称组成;常量的字段值不存在于表单中。要序列化枚举常量,
ObjectOutputStream
请写入枚举常量的 name 方法返回的值。要反序列化枚举常量,ObjectInputStream
请从流中读取常量名称;然后通过调用该java.lang.Enum.valueOf
方法获得反序列化的常量,将常量的枚举类型与接收到的常量名称一起作为参数传递。与其他可序列化或可外部化的对象一样,枚举常量可以充当随后出现在序列化流中的反向引用的目标。枚举常量的序列化过程无法自定义:在序列化和反序列化过程中,枚举类型定义的任何类特定
writeObject
的 、readObject
、readObjectNoData
、writeReplace
和readResolve
方法都将被忽略。类似地,任何serialPersistentFields
或serialVersionUID
字段声明也被忽略——所有枚举类型都有一个固定serialVersionUID
的0L
. 没有必要为枚举类型记录可序列化字段和数据,因为发送的数据类型没有变化。
传统单例的另一个问题是,一旦实现了Serializable
接口,它们就不再保持单例,因为readObject()
方法总是返回一个新实例,就像 Java 中的构造函数一样。这可以通过使用readResolve()
和丢弃新创建的实例来避免,方法是用如下的单例替换:
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
如果您的单例类维护状态,这可能会变得更加复杂,因为您需要使它们成为瞬态的,但是在枚举单例中,JVM 保证了序列化。
好读
在 Java 中创建单例有四种方法。
渴望初始化单例
public class Test { private static final Test test = new Test(); private Test() { } public static Test getTest() { return test; } }
延迟初始化单例(线程安全)
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; } }
带有支架图案的 Bill Pugh 单例(最好是最好的)
public class Test { private Test() { } private static class TestHolder { private static final Test test = new Test(); } public static Test getInstance() { return TestHolder.test; } }
枚举单例
public enum MySingleton { INSTANCE; private MySingleton() { System.out.println("Here"); } }
这是如何实现一个简单的单例:
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();
}
}
如果您需要延迟加载类的实例变量,则需要仔细检查习语。如果您需要延迟加载静态变量或单例,则需要按需初始化持有者习语。
此外,如果单例需要可序列化,则所有其他字段都需要是瞬态的,并且需要实现 readResolve() 方法以保持单例对象不变。否则,每次反序列化对象时,都会创建一个新的对象实例。readResolve() 所做的是替换 readObject() 读取的新对象,这会强制该新对象被垃圾收集,因为没有变量引用它。
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // Original singleton instance.
}
制作单例对象的各种方法:
根据Joshua Bloch - Enum 将是最好的。
您也可以使用双重检查锁定。
甚至可以使用内部静态类。
枚举单例
实现线程安全的单例的最简单方法是使用 Enum:
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
自从在 Java 1.5 中引入 Enum 以来,此代码就可以工作
双重检查锁定
如果你想编写一个在多线程环境中工作的“经典”单例(从 Java 1.5 开始),你应该使用这个。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这在 1.5 之前不是线程安全的,因为 volatile 关键字的实现不同。
早期加载单例(甚至在 Java 1.5 之前也可以使用)
此实现在加载类时实例化单例并提供线程安全。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
对于 JSE 5.0 及更高版本,采用 Enum 方法。否则,使用静态单例持有者方法((Bill Pugh 描述的延迟加载方法)。后一种解决方案也是线程安全的,不需要特殊的语言结构(即 volatile 或 synchronized)。
另一个经常用来反对单例的论点是它们的可测试性问题。单例不容易用于测试目的。如果这是一个问题,我想进行以下轻微修改:
public class SingletonImpl {
private static SingletonImpl instance;
public static SingletonImpl getInstance() {
if (instance == null) {
instance = new SingletonImpl();
}
return instance;
}
public static void setInstance(SingletonImpl impl) {
instance = impl;
}
public void a() {
System.out.println("Default Method");
}
}
添加的setInstance
方法允许在测试期间设置单例类的模型实现:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
这也适用于早期初始化方法:
public class SingletonImpl {
private static final SingletonImpl instance = new SingletonImpl();
private static SingletonImpl alt;
public static void setInstance(SingletonImpl inst) {
alt = inst;
}
public static SingletonImpl getInstance() {
if (alt != null) {
return alt;
}
return instance;
}
public void a() {
System.out.println("Default Method");
}
}
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
这也有将此功能暴露给普通应用程序的缺点。处理该代码的其他开发人员可能会尝试使用“setInstance”方法来更改特定功能,从而更改整个应用程序行为,因此该方法至少应在其 javadoc 中包含一个好的警告。
尽管如此,对于模型测试(需要时)的可能性,这种代码公开可能是可以接受的代价。
最简单的单例类:
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
我仍然认为在 Java 1.5 之后,枚举是可用的最佳单例实现,因为它还确保即使在多线程环境中也只创建一个实例。
public enum Singleton {
INSTANCE;
}
你完成了!
看看这个帖子。
从最佳答案的“单身”部分,
单例(可通过创建方法识别,每次都返回相同的实例(通常是其自身))
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.lang.System#getSecurityManager()
您还可以从 Java 原生类本身学习 Singleton 的示例。
我见过的最好的单例模式使用供应商接口。
- 它是通用且可重复使用的
- 它支持延迟初始化
- 它仅在初始化之前同步,然后将阻塞供应商替换为非阻塞供应商。
见下文:
public class Singleton<T> implements Supplier<T> {
private boolean initialized;
private Supplier<T> singletonSupplier;
public Singleton(T singletonValue) {
this.singletonSupplier = () -> singletonValue;
}
public Singleton(Supplier<T> supplier) {
this.singletonSupplier = () -> {
// The initial supplier is temporary; it will be replaced after initialization
synchronized (supplier) {
if (!initialized) {
T singletonValue = supplier.get();
// Now that the singleton value has been initialized,
// replace the blocking supplier with a non-blocking supplier
singletonSupplier = () -> singletonValue;
initialized = true;
}
return singletonSupplier.get();
}
};
}
@Override
public T get() {
return singletonSupplier.get();
}
}
有时一个简单的“ static Foo 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 立即执行静态 { } - 块。这就是全部秘密:静态块只被调用一次,即给定包的给定类(名称)被这个类加载器加载的时间。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
if (INSTANCE != null)
throw new IllegalStateException(“Already instantiated...”);
}
public synchronized static Singleton getInstance() {
return INSTANCE;
}
}
由于我们在 getInstance 之前添加了 Synchronized 关键字,因此我们避免了两个线程同时调用 getInstance 的情况下的竞争条件。