在 Java 中实现单例模式的有效方法是啥? [关闭]

Posted

技术标签:

【中文标题】在 Java 中实现单例模式的有效方法是啥? [关闭]【英文标题】:What is an efficient way to implement a singleton pattern in Java? [closed]在 Java 中实现单例模式的有效方法是什么? [关闭] 【发布时间】:2010-09-09 09:35:22 【问题描述】:

在 Java 中实现单例设计模式的有效方法是什么?

【问题讨论】:

"在 Java 中实现单例模式的有效方法是什么?"请定义效率。 medium.com/@kevalpatel2106/… 。这是关于如何在单例模式中实现线程、反射和序列化安全的完整文章。这是了解单例类的优点和局限性的好来源。 正如 Joshua Bloch 在 Effective Java 中指出的那样,枚举单例是最好的方法。 Here我将各种实现归类为惰性/渴望等。 【参考方案1】:

使用枚举:

public enum Foo 
    INSTANCE;

Joshua Bloch 在 2008 年 Google I/O 上的 Effective Java Reloaded 演讲中解释了这种方法:link to video。另请参阅他的演示文稿的幻灯片 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));
    

编辑:online portion of "Effective Java" 说:

“这种方法在功能上等同于公共字段方法,只是它更简洁,免费提供序列化机制,并提供针对多次实例化的铁定保证,即使面对复杂的序列化或反射攻击。虽然这种方法尚未被广泛采用,单元素枚举类型是实现单例的最佳方式。”

【讨论】:

我认为人们应该开始将枚举视为具有功能的类。如果您可以在编译时列出类的实例,请使用枚举。 我个人并不经常发现需要直接使用单例模式。我有时将 spring 的依赖注入与包含它所指的单例的应用程序上下文一起使用。我的实用程序类往往只包含静态方法,我不需要它们的任何实例。 嗨,谁能告诉我如何在测试用例中模拟和测试这种类型的单例。我试图为这种类型交换假单例实例,但不能。 我觉得有道理,但我还是不喜欢。你将如何创建一个扩展另一个类的单例?如果您使用枚举,则不能。 @bvdb:如果您想要更大的灵活性,那么您一开始就已经通过实现单例来搞砸了。在需要时创建独立实例的能力本身就是无价的。【参考方案2】:

根据使用情况,有几个“正确”的答案。

从 Java 5 开始,最好的方法是使用枚举:

public enum Foo 
   INSTANCE;

Pre 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 规范确保仅在第一次使用该类时才调用构造函数。

当您有一个非常大的对象或繁重的构造代码并且还有其他可访问的静态方法或字段可能在需要实例之前使用,那么并且只有这样您才需要使用延迟初始化.

您可以使用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() 方法将确保返回唯一的实例,即使该对象在之前的程序运行中被序列化。

【讨论】:

反射检查没有用。如果其他代码对私有使用反射,则游戏结束。在这种滥用情况下,甚至没有理由尝试正常运行。如果你尝试,无论如何这将是一个不完整的“保护”,只是浪费了大量的代码。 > “首先,您希望课程是最终的”。有人可以详细说明一下吗? 反序列化保护完全被破坏了(我认为这在 Effective Java 2nd Ed 中有提到)。 -1 这绝对不是最简单的情况,它是人为的且不必要的复杂。查看 Jonathan 的答案,了解在 99.9% 的所有情况下都足够的实际最简单的解决方案。 这在你的单例需要继承超类时很有用。在这种情况下,您不能使用枚举单例模式,因为枚举不能有超类(尽管它们可以实现接口)。例如,当枚举单例模式不是选项时,Google Guava 使用静态最终字段:code.google.com/p/guava-libraries/source/browse/trunk/guava/src/…【参考方案3】:

免责声明:我刚刚总结了所有很棒的答案并用我自己的话写出来。


在实现单例时,我们有两个选择:

    延迟加载 提前加载

延迟加载会增加一点开销(老实说很多),所以只有当你有一个非常大的对象或繁重的构造代码时才使用它并且还有其他可访问的静态方法或字段可能是在需要实例之前使用,然后并且只有这样你才需要使用延迟初始化。否则,选择提前加载是​​一个不错的选择。

实现单例最简单的方法是:

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 语句,很难理解为什么它是必要的。 详情:The "Double-Checked Locking is Broken" Declaration

现在我们确定了邪恶的线程,但是残酷的连载呢?我们必须确保即使在反序列化时也不会创建新对象:

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 singleton are lazily initialized

这种方法在功能上等同于公共字段方法, 除了更简洁之外,提供了序列化机制 免费,并提供针对多方的铁定保证 实例化,即使面对复杂的序列化或 反射攻击。虽然这种方法尚未被广泛采用, 单元素枚举类型是实现单例的最佳方式。

-《Effective Java》中的约书亚·布洛赫

现在您可能已经意识到为什么 ENUMS 被认为是实现单例的最佳方式,感谢您的耐心等待 :)

在我的blog 上更新了它。

【讨论】:

澄清一下:使用枚举实现的单例是延迟初始化的。详情在这里:***.com/questions/16771373/… 很好的答案。最后一件事,重写 clone 方法以抛出异常。 @xyz 很好的解释,我真的很喜欢并且很容易学会,我希望永远不会忘记这一点 我在 *** 上遇到的最好的答案之一。谢谢! 使用枚举作为单例存在一个序列化问题:任何成员字段值序列化,因此不会恢复。见Java Object Serialization Specification, version 6.0。另一个问题:没有版本控制——所有枚举类型都有固定的serialVersionUID0L。第三个问题:没有自定义:在序列化和反序列化过程中,枚举类型定义的任何特定类的 writeObject、readObject、readObjectNoData、writeReplace 和 readResolve 方法都会被忽略。【参考方案4】:

The solution posted by 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;
    

【讨论】:

很公平!我只是对 volatile 和它的使用感到满意。哦,为 JCiP 欢呼三声。 哦,这显然是 FindBugz 名人的 William Pugh 提倡的方法。 @Stu 第一版 Effective Java(版权所有 2001)在第 48 项下详细介绍了这种模式。 @Bno:将构造函数设为私有怎么样? @AlikElzin-kilaka 不完全是。该实例是在类加载阶段为 BarHolder 创建的,该阶段会延迟到第一次需要时。 Bar 的构造函数可以任意复杂,但直到第一个 getBar() 才会被调用。 (如果getBar 被称为“太早”,那么无论单例如何实现,你都会面临同样的问题。)你可以在这里看到上面代码的延迟类加载:pastebin.com/iq2eayiR【参考方案5】:

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) 推荐的方法,并且可以说更好。去阅读并投票给他的答案。

【讨论】:

在哪里可以了解有关 volatile 修饰符的更多信息? 见***.com/questions/70689/…的cmets 我认为提到反射攻击很重要。诚然,大多数开发人员不需要担心,但似乎像这样的示例(通过基于 Enum 的单例)应该包含防止多实例化攻击的代码,或者简单地放置一个免责声明来表明这种可能性。 这里不需要可变关键字 - 因为同步提供了互斥和内存可见性。 为什么要在 Java 5+ 中处理这一切?我的理解是枚举方法提供线程安全和延迟初始化。它也简单得多......此外,如果你想避免枚举,我仍然会阻止嵌套静态类方法......【参考方案6】:

忘记lazy initialization;这太有问题了。这是最简单的解决方案:

public class A     

    private static final A INSTANCE = new A();

    private A() 

    public static A getInstance() 
        return INSTANCE;
    

【讨论】:

单例实例变量也可以设为final。例如,private static final A singleton = new A(); 这实际上是延迟初始化,因为静态单例在加载类之前不会被实例化,并且在需要它之前不会加载类(这大约是你第一次引用 getInstance() 方法)。 如果类 A 确实在您希望实例化静态之前被加载,您可以将静态包装在静态内部类中以解耦类初始化。 我同意这个答案是最简单的,而且 Anirudhan,没有必要将实例声明为 final。初始化静态成员时,没有其他线程可以访问该类。这是由编译器保证的,换句话说,所有的静态初始化都是以同步的方式完成的——只有一个线程。 这种方法有一个限制:构造函数不能抛出异常。【参考方案7】:

确保您确实需要它。在 Google 上搜索“单例反模式”以查看反对它的一些论据。

我想这本身并没有什么问题,但它只是一种暴露一些全局资源/数据的机制,所以请确保这是最好的方法。特别是,我发现dependency injection (DI) 更有用,特别是如果您还使用单元测试,因为 DI 允许您使用模拟资源进行测试。

【讨论】:

您也可以使用传统方法注入模拟值,但我想这不是标准/srping 方式,因此它的额外工作只会获得遗留代码......【参考方案8】:

我对一些建议 dependency injection (DI) 作为使用单例的替代方案的答案感到困惑;这些是不相关的概念。您可以使用 DI 注入单例或非单例(例如,每线程)实例。至少如果你使用 Spring 2.x 是这样,我不能代表其他 DI 框架。

所以我对 OP 的回答是(除了最琐碎的示例代码之外的所有示例代码):

    使用像Spring Framework这样的DI框架,然后 无论您的依赖项是单例、请求范围、会话范围还是其他任何类型,都将其作为 DI 配置的一部分。

这种方法为您提供了一个很好的解耦(因此灵活且可测试)架构,其中是否使用单例是一个容易可逆的实现细节(当然,前提是您使用的任何单例都是线程安全的)。

【讨论】:

也许是因为人们不同意你的观点。我没有对你投反对票,但我不同意:我认为 DI 可以用来解决单例的相同问题。这是基于将“单例”理解为“通过全局名称直接访问的具有单个实例的对象”,而不仅仅是“具有单个实例的对象”,这可能有点棘手。 稍微扩展一下,考虑一个TicketNumberer,它需要一个全局实例,并且你想在其中编写一个类TicketIssuer,其中包含一行代码int ticketNumber = ticketNumberer.nextTicketNumber();。在传统的单例思维中,前一行代码必须类似于TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;。在 DI 思维中,该类将具有像 public TicketIssuer(TicketNumberer ticketNumberer) this.ticketNumberer = ticketNumberer; 这样的构造函数。 调用那个构造函数就成了别人的问题。 DI 框架将使用某种全球地图来完成。一个手工构建的 DI 架构可以做到这一点,因为应用程序的 main 方法(或其仆从之一)将创建依赖项,然后调用构造函数。本质上,全局变量(或全局方法)的使用只是可怕的service locator pattern 的一种简单形式,并且可以用依赖注入代替,就像该模式的任何其他使用一样。 @TomAnderson 我真的很困惑为什么人们“害怕”服务定位器模式。我认为在大多数情况下它是矫枉过正或充其量不需要,但是,似乎有一些有用的情况。使用较少数量的参数 DI 绝对是首选,但想象一下 20+。说代码不是结构化的不是一个有效的论点,因为有时参数分组是没有意义的。另外,从单元测试的角度来看,我不关心测试服务,只关心它的业务逻辑,如果编码正确,那么这将很容易。我只在非常大型的项目中看到了这种需求。【参考方案9】:

真正考虑一下为什么在编写之前需要一个单例。有一个关于使用它们的准宗教辩论,如果你用谷歌搜索 Java 中的单例,你很容易被绊倒。

就个人而言,出于多种原因,我尽量避免使用单例,其中大部分可以通过谷歌搜索单例找到。我觉得单身人士经常被滥用,因为他们很容易被每个人理解。它们被用作将“全局”数据引入 OO 设计的机制,并且它们被使用是因为它很容易绕过对象生命周期管理(或者真正考虑如何从 B 内部执行 A)。看看 inversion of control (IoC) 或 dependency injection (DI) 之类的东西,寻找一个不错的中间立场。

如果你真的需要一个,那么 Wikipedia 有一个很好的例子来说明如何正确实现单例。

【讨论】:

同意。它更像是启动应用程序其余部分的基础类,如果它被复制,您将最终陷入完全混乱(即对资源的单一访问或强制安全性)。在整个应用程序中传递全局数据是一个很大的耦合危险信号。当你承认你真的需要它时使用它。【参考方案10】:

以下是三种不同的方法

    枚举

     /**
     * 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;
         
     
    

【讨论】:

【参考方案11】:

我使用Spring Framework 来管理我的单身人士。

它不强制执行类的“单一性”(如果涉及多个类加载器,您无论如何都无法真正做到这一点),但它提供了一种非常简单的方法来构建和配置不同的工厂以创建不同类型的对象。

【讨论】:

【参考方案12】:

实现单例有很多细微差别。持有人模式不能在许多情况下使用。并且 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 进行操作的次数,从而收回我们失去的一些性能。请注意,当我们进入同步块时,我们必须再次设置我们的局部变量。这样可以确保在我们等待锁定时发生的任何更改都是最新的。

我最近写了一篇关于这个的文章。 Deconstructing The Singleton。您可以在此处找到有关这些示例的更多信息以及“持有人”模式的示例。还有一个真实示例展示了双重检查 volatile 方法。

【讨论】:

您能否解释一下为什么您的article 中的BearerToken instance 不是staticresult.hasExpired() 是什么? 那么class MySingleton 呢?也许应该是final @Woland BearerToken 实例不是静态的,因为它是 BearerTokenFactory 的一部分 - 它配置有特定的授权服务器。可能有很多BearerTokenFactory 对象——每个对象都有自己的“缓存”BearerToken,直到过期为止。 BeraerToken 上的 hasExpired() 方法在工厂的 get() 方法中调用,以确保它不会分发过期令牌。如果过期,将向授权服务器请求一个新的令牌。代码块后面的段落更详细地解释了这一点。 写得很好,但为什么要使用文字INSTANCE(全部大写)?这不违反 Java 命名约定(即使它可能是某种占位符名称)?找不到更好的名字吗? @PeterMortensen,你知道,我这辈子都不记得为什么我使用全部大写了。哈哈。可能是因为它在设置后就像static final 一样使用。身份证。 ¯\_(ツ)_/¯【参考方案13】:

Wikipedia 有一些 examples 的单例,也使用 Java。 Java 5 实现看起来相当完整,并且是线程安全的(应用了双重检查锁定)。

【讨论】:

【参考方案14】:

版本 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;
    

延迟加载,非阻塞线程安全,高性能。

【讨论】:

【参考方案15】:

如果您不需要延迟加载,那么只需尝试:

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 方法或为您的所有单身人士。

【讨论】:

第一个版本最好。假设这个类除了提供一个单例之外什么都不做,那么由于延迟类加载,它通常会在与第二个版本中的实例大致相同的点被实例化。 双重检查对于静态来说毫无意义。为什么要公开受保护的克隆方法? -1 您的双重检查锁定版本已损坏。 另外你需要让你的单例变量volatile 第一个版本惰性和线程安全的。【参考方案16】:

我会说一个枚举单例。

在 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 方法获得反序列化的常量,将常量的枚举类型与接收到的常量名称一起作为参数传递。与其他可序列化或可外部化的对象一样,枚举常量可以充当随后出现在序列化流中的反向引用的目标。

无法自定义枚举常量序列化的过程:在序列化和反序列化过程中,将忽略枚举类型定义的任何类特定的writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve方法.同样,任何serialPersistentFieldsserialVersionUID 字段声明也将被忽略——所有枚举类型都有固定的serialVersionUID0L。没有必要为枚举类型记录可序列化字段和数据,因为发送的数据类型没有变化。

Quoted from Oracle documentation

传统单例的另一个问题是,一旦实现了Serializable 接口,它们就不再保持单例,因为readObject() 方法总是返回一个新实例,就像Java 中的构造函数一样。这可以通过使用readResolve() 并通过替换为如下单例来丢弃新创建的实例来避免:

 // readResolve to prevent another instance of Singleton
 private Object readResolve()
     return INSTANCE;
 

如果您的单例类保持状态,这可能会变得更加复杂,因为您需要使它们成为瞬态的,但是在枚举单例中,JVM 保证了序列化。


好读

    Singleton Pattern Enums, Singletons and Deserialization Double-checked locking and the Singleton pattern

【讨论】:

【参考方案17】:

在 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");
         
     
    

【讨论】:

(1) 不急切,由于 JVM 类加载机制,它是惰性的。 @Miha_x64 我什么时候说急切加载,我说急切初始化。如果您认为两者相同,那么什么是急切加载。也许您应该写一本书并纠正以前作者所犯的错误,例如约书亚·布洛赫。 Effective Java 是一本很棒的书,但肯定需要编辑。 @Miha_x64 什么是急切加载,你能举例解释一下吗 “急切地”做某事意味着“尽快”。例如,如果明确需要,Hibernate 支持急切地加载关系。【参考方案18】:

这是如何实现一个简单的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;
    

这是如何正确地懒惰地创建你的单例:

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();
    

【讨论】:

两者都很懒惰,假设你唯一需要的单例就是它的实例。 @Miha_x64 第一种情况会在JVM初始化类时实例化单例,第二种情况只会在调用getInstance()时实例化单例。但事实上,如果你的类中没有任何其他静态方法 Singleton 而你只调用 getInstance() 并没有真正的区别。【参考方案19】:

如果您需要延迟加载类的实例变量,则需要 double-checking 习惯用法。如果你需要懒加载静态变量或单例,你需要initialization on demand holder idiom。

此外,如果单例需要可序列化,则所有其他字段都需要是瞬态的,并且需要实现 readResolve() 方法以保持单例对象不变。否则,每次反序列化对象时,都会创建一个新的对象实例。 readResolve() 所做的就是替换 readObject() 读取的新对象,这会强制该新对象被垃圾回收,因为没有变量引用它。

public static final INSTANCE == ....
private Object readResolve() 
  return INSTANCE; // Original singleton instance.
 

【讨论】:

【参考方案20】:

制作单例对象的各种方法:

    根据Joshua Bloch - Enum 是最好的。

    您也可以使用双重检查锁定。

    甚至可以使用内部静态类。

【讨论】:

Re Joshua Bloch:你指的是什么?特定的书或博客文章?请通过editing your answer 回复,而不是在 cmets 中(without "Edit:"、"Update:" 或类似的 - 答案应该看起来像是今天写的)。【参考方案21】:

枚举单例

实现线程安全的单例的最简单方法是使用枚举:

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");
  


【讨论】:

这与之前的答案有何不同? 你应该问其他人这个。如您所见,这是在 2015 年回答的,这是当时最完整的答案:)【参考方案22】:

对于 JSE 5.0 及更高版本,采用 Enum 方法。否则,请使用静态单例持有者方法((Bill Pugh 描述的延迟加载方法)。后一种解决方案也是线程安全的,无需特殊的语言结构(即 volatile 或 synchronized)。

【讨论】:

【参考方案23】:

另一个经常用于反对单例的论点是它们的可测试性问题。为了测试目的,单例不容易模拟。如果这被证明是个问题,我想进行以下轻微修改:

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 中包含一个好的警告。

不过,对于模型测试(需要时)的可能性,这种代码公开可能是可以接受的代价。

【讨论】:

【参考方案24】:

最简单的单例类:

public class Singleton 
  private static Singleton singleInstance = new Singleton();
  private Singleton() 
  public static Singleton getSingleInstance() 
    return singleInstance;
  

【讨论】:

这和下面乔纳森的回答一样 五年前由Jonathan 发布的this sibling answer 的副本。查看有趣的 cmets 的答案。【参考方案25】:

看看这篇文章。

Examples of GoF Design Patterns in Java's core libraries

来自最佳答案的“Singleton”部分,

单例(可通过创建方法识别,每次都返回相同的实例(通常是其自身))

java.lang.Runtime#getRuntime() java.awt.Desktop#getDesktop() java.lang.System#getSecurityManager()

你也可以从Java原生类本身学习Singleton的例子。

【讨论】:

【参考方案26】:

我见过的最好的单例模式使用供应商接口。

通用且可重复使用 支持延迟初始化 它只会在初始化之前进行同步,然后将阻塞供应商替换为非阻塞供应商。

见下文:

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();
    

【讨论】:

【参考方案27】:

我仍然认为在 Java 1.5 之后,枚举是可用的最佳单例实现,因为它还确保即使在多线程环境中也只创建一个实例。

public enum Singleton 
    INSTANCE;

你就完成了!

【讨论】:

这在几年前的其他答案中已经提到过。【参考方案28】:

有时一个简单的“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 - 块。这就是全部秘密:静态块只被调用一次,即给定包的给定类(名称)被这个类加载器加载的时间。

【讨论】:

不正确。加载类时,静态变量与静态块一起初始化。无需拆分声明。【参考方案29】:
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 的情况。

【讨论】:

我认为这不会编译。

以上是关于在 Java 中实现单例模式的有效方法是啥? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如何使用工厂构造函数在 Dart 中实现单例模式?

python中实现单例模式

如何在Qt 中实现单例模式

如何在 C# 中实现单例设计模式? [复制]

Unity3D日常开发Unity3D中实现单例模式详解

在控制台应用程序 C# 中实现单例的最佳方法?