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

时间:2008-09-16 09:24:28

标签: java singleton design-patterns

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

29 个答案:

答案 0 :(得分:755)

使用枚举:

public enum Foo {
    INSTANCE;
}

Joshua Bloch在他在Google I / O 2008的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"说:

  

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

答案 1 :(得分:227)

根据用途,有几个“正确”的答案。

从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()将确保将返回唯一的实例,即使在上一次运行程序中序列化对象时也是如此。

答案 2 :(得分:131)

免责声明:我刚刚总结了所有令人敬畏的答案,并用我的话写下来。


在实施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;
    }
}

这被称为“Double-Checked Locking idiom”。很容易忘记不稳定的陈述,很难理解为什么有必要 有关详细信息: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();
}

不再担心序列化,线程和丑陋的代码。 ENUMS singleton are lazily initialized也是blog

  

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

-Joshua Bloch in“Effective Java”

现在您可能已经意识到为什么ENUMS被认为是实施Singleton的最佳方式,感谢您的耐心:) 在我的{{3}}上更新了它。

答案 3 :(得分:123)

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;
    }
}

答案 4 :(得分:94)

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

答案 5 :(得分:90)

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

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}

答案 6 :(得分:47)

确保你确实需要它。做谷歌的“单身反模式”看一些反对它的论点。我想它没有什么本质上的错误,但它只是一种暴露一些全球资源/数据的机制,所以要确保这是最好的方法。特别是我发现依赖注入更有用,特别是如果你也使用单元测试,因为DI允许你使用模拟资源进行测试。

答案 7 :(得分:21)

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

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

  1. 使用像Spring这样的DI框架,然后
  2. 将其作为DI配置的一部分,无论您的依赖项是单例,请求作用域,会话作用域还是其他。
  3. 这种方法为您提供了一个很好的解耦(因此也是灵活且可测试的)架构,其中是否使用单例是一个易于反转的实现细节(假设您使用的任何单例都是线程安全的)。

答案 8 :(得分:20)

在写之前真的要考虑为什么需要一个单身人士。关于使用它们存在准宗教的争论,如果你用Java编写google singletons,你很容易就会发现它。

就我个人而言,我试图尽可能多地避免单身人士,原因很多,其中大部分都可以通过谷歌搜索单身人士来找到。我觉得很多时候单身人士被滥用,因为他们很容易被所有人理解,他们被用作将“全局”数据转化为OO设计的机制,因为它很容易绕过对象生命周期管理(或者真的在考虑如何从B内部做一个A)。查看控制反转(IoC)或依赖注入(DI)等内容,以获得良好的中间地带。

如果你真的需要一个,那么维基百科有一个很好的例子来正确实现单身人士。

答案 9 :(得分:16)

以下是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;
    }
}

答案 10 :(得分:13)

我使用Spring Framework来管理我的单身人士。它不强制执行类的“单例”(如果涉及多个类加载器,则无论如何都无法实现),但它提供了一种非常简单的方法来构建和配置不同的工厂来创建不同类型的对象。

答案 11 :(得分:11)

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

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

答案 12 :(得分:10)

维基百科有一些examples单身人士,也有Java。 Java 5实现看起来非常完整,并且是线程安全的(应用了双重检查锁定)。

答案 13 :(得分:10)

如果您不需要延迟加载,请尝试

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方法或为所有人创建一个注册表你的单身人士。

答案 14 :(得分:8)

我会说Enum singleton

在Java中使用枚举的单例通常是声明枚举单例的方法。枚举单例可以包含实例变量和实例方法。为简单起见,还要注意,如果您使用的任何实例方法都不需要确保该方法的线程安全,如果它会影响对象的状态。

使用枚举非常容易实现,并且对于可序列化对象没有任何缺点,必须以其他方式规避这些对象。

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

您可以通过Singleton.INSTANCE访问它,比在Singleton上调用getInstance()方法更容易。

  

1.12 Enum常数的序列化

     

枚举常量的序列化与普通的可序列化或可外部化的对象不同。枚举常量的序列化形式仅由其名称组成;常量的字段值不在表单中。要序列化枚举常量,ObjectOutputStream会写入枚举常量名称方法返回的值。要反序列化枚举常量,ObjectInputStream将从流中读取常量名称;然后通过调用java.lang.Enum.valueOf方法获取反序列化常量,将常量的枚举类型与接收到的常量名称一起作为参数传递。与其他可序列化或可外部化的对象一样,枚举常量可以作为随后出现在序列化流中的反向引用的目标。

     

无法自定义枚举常量序列化的过程:任何特定于类的writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve方法在序列化和反序列化期间忽略由枚举类型定义的。同样,任何serialPersistentFieldsserialVersionUID字段声明也会被忽略 - 所有枚举类型都有固定的serialVersionUID 0L。记录枚举类型的可序列化字段和数据是不必要的,因为发送的数据类型没有变化。

     

Quoted from Oracle docs

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

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

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


好读

  1. Singleton Pattern
  2. Enums, Singletons and Deserialization
  3. Double-checked locking and the Singleton pattern

答案 15 :(得分:8)

在游戏中可能会有点迟到,但实施单身人士有很多细微之处。在许多情况下不能使用支架图案。和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的值,然后对local变量进行操作。这将减少我们对易失性操作的次数,从而回收我们失去的一些性能。请注意,当我们进入synchronized块时,我们必须再次设置本地变量。这可确保它在我们等待锁定时发生的任何更改都是最新的。

我最近写了一篇关于此事的文章。 Deconstructing The Singleton。您可以找到有关这些示例的更多信息以及"持有者"的示例。那里的模式。还有一个真实的例子展示了双重检查的volatile方法。希望这会有所帮助。

答案 16 :(得分:7)

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

答案 17 :(得分:4)

这是实现简单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();
    }
}

答案 18 :(得分:3)

如果您需要懒惰地加载类的实例变量,则需要double-checking个用法。 如果您需要懒惰加载静态变量或单例,则需要initilization on demand holder个用户名。

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

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

答案 19 :(得分:3)

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

  1. 按照Joshua Bloch的说法 - Enum将是最好的。

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

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

答案 20 :(得分:3)

枚举单身

实现线程安全的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");
  }

}

答案 21 :(得分:2)

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

答案 22 :(得分:2)

经常用于反对单身人士的另一个论点是他们的可测性问题。单身人士不容易为测试目的而嘲笑。如果这是一个问题,我想做一些小修改:

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

但是,对于模型测试的可能性(如果需要),此代码曝光可能是可接受的代价。

答案 23 :(得分:1)

最简单的单身人士

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

答案 24 :(得分:0)

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

public enum Singleton{ INSTANCE; }

你完成了!!!

答案 25 :(得分:0)

看一下这篇文章。

Examples of GoF Design Patterns in Java's core libraries

从最好的答案" Singleton"部,

  

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

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

您还可以从Java本机类本身学习Singleton的示例。

答案 26 :(得分:0)

我见过的最好的单例模式是使用Supplier接口。

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

参见下文:

public

答案 27 :(得分:-3)

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

答案 28 :(得分:-5)

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的情况下,我们避免了竞争条件。