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

What is an efficient way to implement a singleton pattern in Java?

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


使用枚举:

1
2
3
public enum Foo {
    INSTANCE;
}

Joshua Bloch在谷歌I/O 2008的有效Java重载会话中解释了这种方法:链接到视频。另请参见他的演讲幻灯片30-32(effective_java_reloaded.pdf):

The Right Way to Implement a Serializable Singleton

1
2
3
4
5
6
7
8
public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        {"Hound Dog","Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

编辑:"有效Java"的在线部分说:

"This approach is functionally equivalent to the public field approach, except that it is more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks. While this approach has yet to be widely adopted, a single-element enum type is the best way to implement a singleton."


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

因为java5最好的方法是使用枚举:

1
2
3
public enum Foo {
   INSTANCE;
}

在Java5之前,最简单的情况是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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来加载实例。然后代码看起来像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();仅在实际使用类装填器时执行,因此这将处理懒惰的实例化,并确保线程安全。

当您还希望能够序列化对象时,您需要确保反序列化不会创建副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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时,我们有两个选项1。延迟加载2。早期加载

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

实现单例的最简单方法是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
    }
}

一切都很好,除了它的早期加载单件。让我们试试懒惰加载的单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
    }
}

到目前为止一切都很好,但是我们的英雄在与多条邪恶的线单独战斗的时候是无法生存的,他们想要我们英雄的许多实例。所以让我们保护它免受邪恶的多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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. cd.und.euu/~pug/java/MeimyMy/DouthCuffkLogLojk.HTML

现在我们确定了邪恶的线索,但是残酷的系列化呢?我们必须确保即使在取消序列化时也不会创建新对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()将确保返回唯一的实例,即使在以前的程序运行中序列化了对象。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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时执行,因此这将处理延迟实例化,

它保证线程安全吗?

我们已经走了这么远,这是实现我们所做一切的最好方法,也是最好的可能方法。

1
2
3
 public enum Foo {
       INSTANCE;
   }

内部将被视为

1
2
3
4
5
public class Foo {

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

就是这样,不再害怕序列化、线程和丑陋的代码。另外,枚举singleton被延迟初始化。

This approach is functionally equivalent to the public field approach,
except that it is more concise, provides the serialization machinery
for free, and provides an ironclad guarantee against multiple
instantiation, even in the face of sophisticated serialization or
reflection attacks. While this approach has yet to be widely adopted,
a single-element enum type is the best way to implement a singleton.

"有效Java"中的Joshua Bloch

现在您可能已经认识到为什么Enums被认为是实现单例的最佳方法,感谢您的耐心:)在我的博客上更新了它。


stu thompson发布的解决方案在java5.0及更高版本中有效。但我不喜欢使用它,因为我认为它很容易出错。

很容易忘记易变的说法,也很难理解为什么有必要这样做。如果没有volatile,由于双重检查锁定反模式,此代码将不再是线程安全的。在实践中,在Java并发的第16.2.4段中更多地了解这一点。简而言之:此模式(在java5.0之前或没有volatile语句)可能返回对处于(仍然)不正确状态的bar对象的引用。

这个模式是为性能优化而发明的。但这已经不再是真正的担忧了。以下懒惰的初始化代码速度快,而且更重要的是更容易读取。

1
2
3
4
5
6
7
8
9
class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}


线程安全在Java 5 +中:

1
2
3
4
5
6
7
8
9
10
11
12
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)推荐的方法,并且可以说是更好的方法。去阅读并投票表决他的答案。


忘记懒惰的初始化,这太麻烦了。这是最简单的解决方案:

1
2
3
4
5
6
7
8
9
10
public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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


确保你真的需要它。做一个谷歌的"单件反模式",看看一些反对它的论点。我想这并没有什么本质上的问题,但它只是一种公开一些全球资源/数据的机制,所以要确保这是最好的方法。特别是,我发现依赖注入更有用,特别是当您还使用单元测试时,因为DI允许您使用模拟资源进行测试。


别忘了,对于加载它的类加载器,singleton只是一个singleton。如果您使用多个装载机(容器),那么每个装载机都可以有自己的单例版本。


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

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

  • 然后使用像Spring这样的DI框架
  • 使它成为DI配置的一部分,不管您的依赖关系是单例的、请求范围的、会话范围的,还是其他的。
  • 这种方法为您提供了一个很好的解耦(因此是灵活的和可测试的)架构,其中是否使用单例是一个容易可逆的实现细节(当然,只要您使用的任何单例都是线程安全的)。


    在写之前真的要考虑一下为什么你需要一个单身汉。如果你在Java中使用谷歌独生子,你可以很容易地遇到这些问题。

    就我个人而言,我尽量避免单身,原因有很多,其中大部分都可以通过谷歌搜索找到。我觉得单件产品经常被滥用,因为每个人都很容易理解它们,它们被用作将"全局"数据引入OO设计的机制,它们被使用是因为很容易绕过对象生命周期管理(或者真正考虑如何从B内部进行a)。看看控制反转(IOC)或依赖注入(DI)之类的东西,找到一个很好的中间平台。

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


    以下是3种不同的方法

    1)枚举

    1
    2
    3
    4
    5
    6
    /**
    * Singleton pattern example using Java Enumj
    */

    public enum EasySingleton{
        INSTANCE;
    }

    2)双重检查锁定/延迟加载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * 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)静态工厂法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 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;
        }
    }

    我使用Spring框架来管理我的单例。它不强制类的"单一性"(如果涉及多个类加载器,则无论如何都不能这样做),但提供了一种非常简单的方法来构建和配置不同的工厂以创建不同类型的对象。


    版本1:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MySingleton {
        private MySingleton() {}
        private static class MySingletonHolder {
            public final static MySingleton instance = new MySingleton();
        }
        public static MySingleton getInstance() {
            return MySingletonHolder.instance;
        }
    }

    懒惰加载,线程安全,无阻塞,高性能。


    维基百科在爪哇也有一些单身人士的例子。Java 5实现看起来相当完整,并且是线程安全的(双重检查锁定应用)。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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是线程安全的,请尝试双重检查模式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Singleton {
            private static Singleton instance = null;

            private Singleton() {}

            public static Singleton getInstance() {
                  if(null == instance) {
                      synchronized(Singleton.class) {
                          if(null == instance) {
                              instance = new Singleton();
                          }
                      }
                   }
                   return instance;
            }

            protected Object clone() {
                throw new CloneNotSupportedException();
            }
    }

    由于双重检查模式无法保证工作(由于编译器的某些问题,我对此一无所知),您还可以尝试同步整个getInstance方法或为所有的单例创建注册表。


    我想说枚举单例

    在Java中使用EnUM的单例通常是声明Enm SuntLon的方式。枚举singleton可以包含实例变量和实例方法。为了简单起见,还要注意,如果您使用的是任何实例方法,那么您需要确保该方法的线程安全(如果它影响到对象的状态的话)。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * Singleton pattern example using Java Enum
    */

    public enum Singleton {
            INSTANCE;
            public void execute (String arg) {
                    //perform operation here
            }
    }

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

    1.12 Serialization of Enum Constants

    Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream.

    The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored--all enum types have a fixed serialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

    Quoted from Oracle docs

    传统的单体的另一个问题是,一旦实现了EDCOX1×2接口,它们就不再保持单点,因为EDCOX1×3的方法总是返回Java中的构造函数之类的新实例。这可以通过使用readResolve()和使用下面的singleton替换新创建的实例来避免。

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

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

    好读

  • 单例模式
  • 枚举、单例和反序列化
  • 双重检查锁定和单件模式

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    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");
        }
    }


    在这个问题上,可能有点晚了,但是实现一个单例有很多细微差别。夹持器模式不能在许多情况下使用。在使用volatile时,也应该使用局部变量。让我们从一开始就重复这个问题。你会明白我的意思的。好的。

    第一次尝试可能是这样的:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MySingleton {

         private static MySingleton INSTANCE;

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

            return INSTANCE;
        }
        ...
    }

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private static MySingleton INSTANCE;

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

        return INSTANCE;
    }

    这里我们使用方法签名中的synchronized关键字来同步getInstance()方法。这肯定会改善我们的比赛条件。线程现在将一次阻塞并输入一个方法。但它也会造成性能问题。此实现不仅同步单个实例的创建,还同步对getInstance()的所有调用,包括读取。读取不需要同步,因为它们只返回实例的值。因为reads将占我们调用的大部分(记住,实例化只在第一个调用上发生),所以通过同步整个方法,我们将导致不必要的性能损失。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private static MySingleton INSTANCE;

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

        return INSTANCE;
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private static MySingleton INSTANCE;

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

        return INSTANCE;
    }

    在这里,我们从街区内再开出一张支票。如果已经设置了实例成员,我们将跳过初始化。这叫做双重检查锁定。好的。

    这就解决了多重实例化的问题。但我们的解决方案再一次提出了另一个挑战。其他线程可能看不到实例成员已被更新。这是因为Java如何优化内存操作。线程将变量的原始值从主内存复制到CPU的缓存中。然后,对值的更改将写入该缓存并从中读取。这是为优化性能而设计的Java特性。但这给我们的单例实现带来了一个问题。第二条线?-?由不同的CPU或核心处理,使用不同的缓存?-?将看不到第一个所做的更改。这将导致第二个线程将实例成员视为空,从而强制创建singleton的新实例。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private static volatile MySingleton INSTANCE;

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

        return INSTANCE;
    }

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

    但这种简单的改变是有代价的。因为我们绕过了CPU缓存,所以每次在易失性实例成员上操作时都会受到性能影响?-?我们做了4次。我们再次检查是否存在(1和2),设置值(3),然后返回值(4)。有人可能会说,这条路径是边缘情况,因为我们只在方法的第一次调用期间创建实例。也许对创作的影响是可以容忍的。但即使我们的主要用例reads也会对易失性成员进行两次操作。一次检查是否存在,然后再次返回其值。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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;
    }

    由于性能下降是由于直接在易失性成员上操作造成的,所以让我们将局部变量设置为易失性的值,并改为在局部变量上操作。这将减少我们在易失性上操作的次数,从而恢复一些丢失的性能。请注意,当我们进入同步块时,必须再次设置本地变量。这样可以确保它是最新的,并且在等待锁时发生了任何更改。好的。

    我最近写了一篇关于这个的文章。解构单重子。您可以在这里找到关于这些示例和"holder"模式的示例的更多信息。还有一个真实的例子展示了双重检查的易失性方法。希望这有帮助。好的。好啊。


    这是如何实现一个简单的singleton

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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();
        }
    }


    创建singleton对象的各种方法:

  • 根据约书亚·布洛赫的说法,枚举是最好的。

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

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


  • 埃努姆辛格尔顿

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

    1
    2
    3
    4
    5
    6
    public enum SingletonEnum {
      INSTANCE;
      public void doSomething(){
        System.out.println("This is a singleton");
      }
    }

    自从在Java 1.5中引入了EnUM,这个代码就起作用了。

    双重检查锁定

    如果你想在多线程环境中(从Java 1.5开始)编写一个"经典"单体,你应该使用这个。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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之前)

    这个实现在加载类时实例化单例,并提供线程安全性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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需要是seriliazble,则所有其他字段都需要是瞬态的,并且需要实现readresolve()方法以保持singleton对象不变。否则,每次反序列化对象时,都将创建对象的新实例。readresolve()所做的是替换readObject()读取的新对象,因为没有引用该对象的变量,所以它强制对该新对象进行垃圾收集。

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

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


    另一个经常用来反对单例的论点是它们的可测试性问题。单件不容易模仿测试的目的。如果这是一个问题,我想做如下的小修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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方法允许在测试期间设置singleton类的模型实现:

    1
    2
    3
    4
    5
    6
    7
    8
    public class SingletonMock extends SingletonImpl {

        @Override
        public void a() {
            System.out.println("Mock Method");
        }

    }

    这也适用于早期的初始化方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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中至少应包含一个好的警告。

    然而,对于模型测试的可能性(当需要时),这个代码暴露可能是一个可接受的代价。


    最简单的单例类

    1
    2
    3
    4
    5
    6
    7
    public class Singleton {
      private static Singleton singleInstance = new Singleton();
      private Singleton() {}
      public static Singleton getSingleInstance() {
        return singleInstance;
      }
    }


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

    • 它是通用的和可重用的
    • 它支持延迟初始化
    • 只同步到初始化完成,然后将阻塞供应商替换为非阻塞供应商。

    见下文:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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();
        }
    }

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

    public enum Singleton{
    INSTANCE;
    }

    你完了!!!!


    看看这篇文章。

    Java核心库中GOF设计模式的实例

    从最佳答案的"单身"部分,

    Singleton (recognizeable by creational methods returning the same instance (usually of itself) everytime)

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

    您还可以从Java原生类本身学习单实例。


    有时一个简单的"static Foo foo = new Foo();"是不够的。只需考虑一些您想要做的基本数据插入。

    另一方面,您必须同步实例化singleton变量的任何方法。同步并不是很糟糕,但它可能导致性能问题或锁定(在使用此示例的极少数情况下)。解决方案是

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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执行静态-块。这就是全部的秘密:静态块只被调用一次,即这个一类装入器装入给定包的给定类(名称)的时间。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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的情况下,我们避免了争用条件。