在Java中实现单例模式的有效方法是什么?
在Java中实现单例模式的有效方法是什么?
Answers:
使用一个枚举:
public enum Foo {
INSTANCE;
}
约书亚·布洛赫(Joshua Bloch)在Google I / O 2008上的“ 有效的Java重新加载”演讲中解释了这种方法:链接到视频。另见幻灯片他的介绍(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)); } }
“该方法在功能上等同于公共领域方法,除了它更简洁,免费提供序列化机制,甚至在面对复杂的序列化或反射攻击时也提供了针对多重实例化的坚定保证。尚未被广泛采用的单元素枚举类型是实现单例的最佳方法。”
根据用法,有几个“正确”的答案。
由于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规范确保仅在首次使用该类时才调用构造函数。
如果您有一个很大的对象或繁重的构造代码,并且还需要在需要实例之前使用其他可访问的静态方法或字段,则仅在那时才需要使用惰性初始化。
您可以使用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
现在我们可以确定邪恶线程,但是残酷的序列化又如何呢?我们必须确保即使反序列化也不会创建新对象
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();
}
而已!不再担心序列化,线程和丑陋的代码。此外枚举单被延迟初始化。
这种方法在功能上与公共领域方法等效,除了它更简洁,免费提供序列化机制,甚至针对复杂的序列化或反射攻击,还提供了针对多重实例化的明确保证。尽管此方法尚未广泛采用,但单元素枚举类型是实现单例的最佳方法。
-“有效Java”中的约书亚·布洛赫(Joshua Bloch)
现在您可能已经意识到为什么将ENUMS视为实现Singleton的最佳方法了,并感谢您的耐心:)
在我的博客上进行了更新。
serialVersionUID
的0L
。第三个问题:没有自定义:枚举类型定义的任何特定于类的writeObject,readObject,readObjectNoData,writeReplace和readResolve方法在序列化和反序列化期间都会被忽略。
Stu Thompson发布的解决方案在Java5.0及更高版本中有效。但是我不希望使用它,因为我认为它容易出错。
容易忘记易失性陈述,也很难理解为什么这样做是必要的。如果没有volatile,由于经过了双重检查的锁定反模式,该代码将不再是线程安全的。有关更多信息,请参见《Java并发实践》第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;
}
}
getBar()
。(如果getBar
被称为“过早”,那么无论如何实现单例,您都将面临相同的问题。)您可以在此处看到上面代码的延迟类加载: pastebin.com/iq2eayiR
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的答案详细介绍了比尔·普格(FindBugs)建议的方法,并且可以说是更好的方法。去阅读并投票支持他的答案。
忘了懒惰的初始化,这太成问题了。这是最简单的解决方案:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
我对某些答案感到迷惑不解,这些答案建议使用DI作为使用单例的替代方法。这些是不相关的概念。您可以使用DI注入单例或非单例(例如,每线程)实例。至少如果您使用Spring 2.x,这是正确的,我不能代表其他DI框架。
因此,我对OP的回答将是(除了最琐碎的示例代码之外):
这种方法为您提供了一个很好的解耦(因此是灵活且可测试的)体系结构,其中是否使用单例是一个易于逆转的实现细节(前提是您使用的任何单例都是线程安全的)。
TicketNumberer
需要具有单个全局实例的实例,以及要在其中编写TicketIssuer
包含一行代码的类的位置int ticketNumber = ticketNumberer.nextTicketNumber();
。在传统的单例思维中,上一行代码必须类似于TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
。在DI思维中,类将具有类似的构造函数public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
。
真正考虑一下为什么在编写之前需要单身人士。关于使用它们的准宗教辩论,如果您用Java搜索单身人士,就很容易绊倒。
就我个人而言,出于多种原因,我尝试尽可能多地避免单身人士,而大多数原因可以通过对单身人士进行搜索来找到。我觉得很多时候滥用单例是因为每个人都容易理解它们,它们被用作一种将“全局”数据输入到OO设计中的机制,并且它们被使用是因为它很容易规避对象生命周期管理(或者真正在考虑如何从B内部做A)。查看诸如控制反转(IoC)或依赖注入(DI)之类的东西可以找到一个很好的中间立场。
如果您真的需要一个,那么维基百科提供了一个正确实现单例的很好的例子。
以下是3种不同的方法
1)枚举
/**
* 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;
}
}
版本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();
}
}
如果您希望延迟加载并且希望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方法或为所有Singleton创建一个注册表。
volatile
我会说Enum singleton
在Java中使用枚举的Singleton通常是声明枚举Singleton的方法。枚举单例可能包含实例变量和实例方法。为简单起见,还请注意,如果您正在使用任何实例方法,则如果该方法完全影响对象的状态,则需要确保该方法的线程安全。
枚举的使用非常容易实现,并且对于可序列化的对象没有任何缺点,而必须以其他方式加以避免。
/**
* 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
写入由枚举常量的name方法返回的值。要反序列化枚举常量,请ObjectInputStream
从流中读取常量名称。然后,通过调用java.lang.Enum.valueOf
方法获得反序列化常量,将常量的枚举类型以及接收到的常量名称作为参数传递。像其他可序列化或可外部化的对象一样,枚举常量可以用作随后出现在序列化流中的反向引用的目标。通过枚举常数被序列过程中不能被定制:任何类特异性
writeObject
,readObject
,readObjectNoData
,writeReplace
,和readResolve
由枚举类型定义的方法被序列化和反序列化期间被忽略。同样,任何serialPersistentFields
或serialVersionUID
字段声明也将被忽略-所有枚举类型都有一个固定serialVersionUID
的0L
。不需要记录枚举类型的可序列化字段和数据,因为发送的数据类型没有变化。
传统Singleton的另一个问题是,一旦实现了Serializable
接口,它们便不再保留Singleton,因为readObject()
方法总是返回新的实例,例如Java中的构造函数。可以通过使用readResolve()
如下所示的单例替换来使用和丢弃新创建的实例来避免这种情况
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
如果您的Singleton Class保持状态,这可能变得更加复杂,因为您需要使它们成为瞬态,但是在Enum Singleton中,JVM保证了序列化。
好读
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");
}
}
在这方面可能要晚一些,但是实现单例有很多细微差别。持有人模式无法在许多情况下使用。和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;
}
在这里,我们在方法签名中使用了synced关键字来同步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)。有人可能会说这条路径是边缘情况,因为我们仅在方法的第一次调用期间创建实例。也许对创作的性能冲击是可以容忍的。但是,即使是我们的主要用例,也将对易失性成员运行两次。一次检查是否存在,再次返回其值。
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的值,然后对局部变量进行操作。这将减少我们对挥发物进行操作的次数,从而收回我们损失的部分性能。注意,当我们进入同步块时,我们必须再次设置局部变量。这样可以确保它与我们等待锁时发生的任何更改都是最新的。
我最近写了一篇有关此的文章。解构Singleton。您可以在这些示例中找到更多信息以及“ holder”模式的示例。还有一个真实的示例,展示了双重检查的易失性方法。希望这可以帮助。
class MySingleton
也许应该是final
?
BearerToken
实例不是静态的,因为它是-的一部分,该实例是BearerTokenFactory
使用特定的授权服务器配置的。可能有很多BearerTokenFactory
对象-每个对象都有自己的“缓存” BearerToken
,分发出去直到过期。在hasExpired()
对方法BeraerToken
是所谓的工厂的get()
方法,以确保它不会伸手过期的令牌。如果过期,将向授权服务器请求一个新令牌。代码块后面的段落对此进行了更详细的说明。
这是实现简单的方法singleton
:
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;
}
}
这是正确延迟创建您的方法singleton
:
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();
}
}
getInstance()
。但是确实,如果您的类中没有任何其他静态方法,Singleton
而仅调用getInstance()
它,则没有真正的区别。
如果需要延迟加载类的实例变量,则需要仔细检查习惯用法。如果需要延迟加载静态变量或单例,则需要按需持有者习惯进行初始化。
另外,如果单例需要可序列化,则所有其他字段都必须是瞬态的,并且需要实现readResolve()方法以保持单例对象不变。否则,每次反序列化对象时,都会创建该对象的新实例。readResolve()所做的是用readObject()替换读取的新对象,由于没有变量引用该对象,因此该对象被强制垃圾回收。
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
制作单例对象的多种方法:
根据Joshua Bloch的说法,Enum是最好的。
您也可以使用双重检查锁定。
甚至可以使用内部静态类。
枚举单例
实现线程安全的Singleton的最简单方法是使用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关键字的实现不同。
早期加载Singleton(甚至在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");
}
}
通常反对Singleton的另一个论点是它们的可测试性问题。单例不容易被嘲笑用于测试目的。如果发现这是个问题,我想做一下以下修改:
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中至少包含一个良好的警告。
尽管如此,为了进行模型测试(必要时),此代码公开可能是可以接受的价格。
我仍然认为在Java 1.5之后,枚举是可用的最佳可用单例实现,因为它还确保即使在多线程环境中也只能创建一个实例。
public enum Singleton{
INSTANCE;
}
你完成了!!!
看看这篇文章。
在最佳答案的“单身”部分中,
单例(可通过创建方法识别,每次返回相同实例(通常是其自身))
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.lang.System#getSecurityManager()
您还可以从Java本机类本身学习Singleton的示例。
我见过的最好的单例模式使用Supplier接口。
见下文:
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会立即执行静态{} -块。这就是全部秘密:在此类加载器加载给定包的给定类(名称)时,只会调用一次static-block。
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的情况下的竞争情况。