it-swarm.cn

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

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

765
Riyaz Mohammed Ibrahim

使用枚举:

public enum Foo {
    INSTANCE;
}

Joshua Bloch在他的 Effective Java Reloaded Google I/O 2008上的讲话中解释了这种方法: 链接到视频 。另见他演示文稿的幻灯片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));
    }
}

编辑: / “有效Java”的在线部分 说:

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

753
Stephen Denne

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

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

public enum Foo {
   INSTANCE;
}

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

227
Roel Spilker

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


在实施Singleton时,我们有2个选项
1。懒加载
2。早装

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

实现Singleton最简单的方法是

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT
            throw new IllegalStateException("Already instantiated");
        }
    }

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

除了早期装载的单身人士外,一切都很好。让我们尝试延迟加载单身

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT  
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

到目前为止一切都那么好但我们的英雄无法生存,而单独与多个邪恶的线程进行战斗,他们需要我们英雄的许多实例。所以我们要保护它免受邪恶的多线程攻击

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

但它不足以保护英雄,真的!这是我们能够/应该做的最好的帮助我们的英雄

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

这被称为“双重锁定成语”。很容易忘记易变的陈述,很难理解为什么有必要。
详情: http://www.cs.umd.edu/~pugh/Java/memoryModel/DoubleCheckedLocking.html

现在我们确定邪恶的线索,但残酷的序列化怎么样?我们必须确保即使在de-serialiaztion中也没有创建新对象

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

方法readResolve()将确保将返回唯一的实例,即使该对象在我们的程序的上一次运行中被序列化也是如此。

最后,我们为线程和序列化添加了足够的保护,但我们的代码看起来很庞大和丑陋。让我们的英雄弥补

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    // Damn you serialization
    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

是的,这是我们非常相同的英雄:)
由于private static final Foo INSTANCE = new Foo();行仅在实际使用类FooLoader时执行,因此它负责惰性实例化,

并保证是线程安全的。

我们到目前为止,这是实现我们所做的一切最好的方式

 public enum Foo {
       INSTANCE;
   }

哪个内部将被视为

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

这就是不再担心序列化,线程和丑陋的代码。另外 ENUMS单例被懒惰地初始化

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

-Joshua Bloch in“Effective Java”

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

130
xyz

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;
    }
}
123
Benno Richters

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

_ edit _ :注意这里的volatile修饰符。 :)这很重要,因为没有它,JMM(Java内存模型)无法保证其他线程看到其值的更改。同步处理它 - 它只序列化对该代码块的访问。

编辑2 :@Bno的回答详细介绍了Bill Pugh(FindBugs)推荐的方法,可以说是更好的。去阅读并投票他的答案。

94
Stu Thompson

忘记 延迟初始化 ,它太有问题了。这是最简单的解决方案:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

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

47
Neil Burroughs

不要忘记Singleton只是加载它的Classloader的Singleton。如果您使用多个加载器(容器),则每个COULD都有自己的Singleton版本。

26
Javamann

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

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

  1. 然后使用像Spring这样的DI框架
  2. 使其成为DI配置的一部分,无论您的依赖项是单例,请求作用域,会话作用域还是其他。

这种方法为您提供了一个Nice解耦(因此灵活且可测试)的体系结构,其中是否使用单例是一个易于反转的实现细节(假设您使用的任何单例都是线程安全的)。

21
Andrew Swan

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

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

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

20
Aidos

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

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

13
Matt

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

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

11
coderz

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

10
macbirdie

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

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

10
Aleksi Yrttiaho

在这个问题上可能会有点晚了,但是在实施单身人士方面存在很多细微差别。在许多情况下不能使用支架图案。和IMO在使用volatile时 - 你也应该使用局部变量。让我们从头开始并迭代问题。你会明白我的意思。


第一次尝试可能看起来像这样:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

这里我们有MySingleton类,它有一个名为INSTANCE的私有静态成员,以及一个名为getInstance()的公共静态方法。第一次调用getInstance()时,INSTANCE成员为null。然后,流将进入创建条件并创建MySingleton类的新实例。对getInstance()的后续调用将发现已经设置了INSTANCE变量,因此不会创建另一个MySingleton实例。这确保了只有一个MySingleton实例在getInstance()的所有调用者之间共享。

但是这个实现有一个问题。多线程应用程序将在创建单个实例时具有竞争条件。如果多个执行线程同时(或大约)同时命中getInstance()方法,它们将各自看到INSTANCE成员为null。这将导致每个线程创建一个新的MySingleton实例,然后设置INSTANCE成员。


private static MySingleton INSTANCE;

public static synchronized MySingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MySingleton();
    }

    return INSTANCE;
}

这里我们使用方法签名中的synchronized关键字来同步getInstance()方法。这肯定会解决我们的竞争状况。线程现在将阻止并一次输入一个方法。但它也会产生性能问题。此实现不仅同步单个实例的创建,还将所有调用同步到getInstance(),包括读取。读取不需要同步,因为它们只返回INSTANCE的值。由于读取将构成我们调用的大部分(请记住,实例化仅在第一次调用时发生),我们将通过同步整个方法来产生不必要的性能损失。


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

在这里,我们将同步从方法签名移动到包装MySingleton实例创建的同步块。但这能解决我们的问题吗?好吧,我们不再阻止阅读,但我们也向前退了一步。多个线程将同时或大约同时命中getInstance()方法,并且它们都将INSTANCE成员视为null。然后,他们将点击同步块,其中一个将获得锁并创建实例。当该线程退出该块时,其他线程将争用该锁,并且每个线程将逐个通过该块并创建该类的新实例。所以我们回到了我们开始的地方。


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

在这里,我们从INSIDE块发出另一张支票。如果已经设置了INSTANCE成员,我们将跳过初始化。这称为双重检查锁定。

这解决了我们多实例化的问题。但是,我们的解决方案又一次提出了另一项挑战。其他线程可能不会“看到”INSTANCE成员已更新。这是因为Java优化了内存操作。线程将变量的原始值从主存储器复制到CPU的缓存中。然后,将对值的更改写入该缓存并从中读取。这是Java的一项功能,旨在优化性能。但这给我们的单例实现带来了问题。第二个线程 - 由不同的CPU或核心使用不同的缓存处理 - 将不会看到第一个线程所做的更改。这将导致第二个线程将INSTANCE成员视为null,从而强制创建我们的单例的新实例。


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

我们通过在INSTANCE成员的声明中使用volatile关键字来解决这个问题。这将告诉编译器始终读取和写入主内存,而不是CPU缓存。

但这种简单的改变需要付出代价。因为我们绕过CPU缓存,所以每次操作易失性INSTANCE成员时我们都会受到性能影响 - 我们会这样做4次。我们仔细检查存在(1和2),设置值(3),然后返回值(4)。有人可能认为这条路径是边缘情况,因为我们只在第一次调用方法时创建实例。也许创作的表现受到了影响。但即使是我们的主要用例read也会对volatile组件进行两次操作。一旦检查存在,再次返回其值。


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

由于性能损失是由于直接在volatile成员上运行,因此让我们将局部变量设置为volatile的值,而不是对局部变量进行操作。这将减少我们对易失性操作的次数,从而回收我们失去的一些性能。请注意,当我们进入synchronized块时,我们必须再次设置本地变量。这可确保它在我们等待锁定时发生的任何更改都是最新的。

我最近写了一篇关于此的文章。 解构单身人士 。您可以在这些示例中找到更多信息,并在那里找到“持有者”模式的示例。还有一个真实的例子展示了双重检查的volatile方法。希望这可以帮助。

8
Michael Andrews

我会说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枚举常量的序列化

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

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

引用Oracle文档

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

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

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


好读

  1. 单身人士模式
  2. 枚举,单身人士和反序列化
  3. 双重检查锁定和Singleton模式
8
NullPoiиteя
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");
    }
}
7
Dheeraj Sachan

这是如何实现一个简单的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();
    }
}
4
Nicolas Filotto

如果需要懒惰​​地加载类的实例变量,则需要 重复检查 习惯用法。如果你需要懒惰地加载一个静态变量或一个单例,你需要 按需启动 idiom。

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

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

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

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

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

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

3
Shailendra Singh

Enum singleton

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

}
3
Dan Moldovan

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

2
raoadnan

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

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

但是,对于模型测试(在需要时)的可能性,此代码暴露可能是可接受的支付价格。

2
user3792852

最简单的单身人士课程

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

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

public enum Singleton{ INSTANCE; }

你完成了!

0
shikjohari

看看这篇文章。

Java核心库中的GoF设计模式示例

从最佳答案的“Singleton”部分,

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

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

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

0
itiskj

我见过的最好的单例模式使用了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();
    }
}
0
user1024314