简体   繁体   English

这两种单例实现之间有什么区别?

[英]What is the difference between these two implementation of singleton?

I am having problem to understand the difference between these two function. 我在理解这两个功能之间的区别时遇到问题。 Aren't they both implementing singleton? 他们不是都实施单例吗? If so whats the benefit over one another. 如果是这样,那又有什么好处呢?

    private static GameManager instance = new GameManager();
    public static GameManager get(){return instance;}

and the bellow, 还有吼叫

    private static GameManager _instance;

    public static GameManager instance(){
     if(_instance == null) _instance = new GameManager();
     return _instance;
    }

They are both implemented as singleton. 它们都实现为单例。 The only difference is that the Singleton instance is Lazy Loaded on the second code block. 唯一的区别是,Singleton实例在第二个代码块上是延迟加载的。 In other words, it won't initialize the GameManager until instance method is called. 换句话说,它不会在调用instance方法之前初始化GameManager。

You should also probably rewrite your code to something simpler such as: 您可能还应该将代码重写为更简单的内容,例如:

public static GameManager instance = new GameManager();

The difference is that the second implementation is no good in multi-threaded environment because it may create more than one instance. 不同之处在于第二种实现在多线程环境中不好,因为它可能创建多个实例。 This is when two threads check if instance == null at the same time and both get true 这是两个线程同时检查instance == null并都为真时

Note that option #1 can also be lazy: 请注意,选项#1也可以是懒惰的:

class GameManager {
    private static GameManager instance = new GameManager();

    private GameManager() {
        System.out.println("instance created");
    }

    public static GameManager getInstance() {
        return instance;
    }
}

Try to use it and you will see that instance is created only when we first call getInstance() 尝试使用它,您将看到仅当我们第一次调用getInstance()时才创建该实例。

There are two main differences: 有两个主要区别:

  1. The first example is "eager", and the second one is "lazy". 第一个示例是“渴望”,第二个示例是“懒惰”。 Specifically, the first one will create the singleton object as soon as the singleton class is initialized, but the second one will create the singleton object when instance() is called the first time. 具体来说,第一个将在单例类初始化后立即创建单例对象,但是第二个将在首次调用instance()时创建单例对象。

    Generally speaking, eager initialization is simpler. 一般来说, 渴望初始化会更简单。 However, it may be the case that initialization may depend on other things, and lazy initialization provides a way to defer the initialization until they have happened. 但是,可能存在初始化可能依赖于其他情况的情况,而惰性初始化提供了一种将初始化推迟到发生之前的方法。

  2. The second example has an insidious problem if two or more threads can call instance() simultaneously. 如果两个或多个线程可以同时调用instance()则第二个示例存在一个隐患。 Specifically, the two threads might get different GameManager objects. 具体来说,两个线程可能会获得不同的GameManager对象。

    The analogous problem in the first example is that one thread might see a null if there is an issue with class initialization cycles in the application. 第一个示例中的类似问题是,如果应用程序中的类初始化周期存在问题,则一个线程可能会看到null That could lead to one thread seeing a null value. 这可能导致一个线程看到null值。 However, the semantics of class initialization mean that there is a happens-before relation between class initialization and calling a method on the initialized class. 但是,类初始化的语义意味着在类初始化和在已初始化的类上调用方法之间存在先发生后关系。 Therefore, both threads are guaranteed to see the correct initial state for the GameManager object. 因此,保证两个线程都能看到GameManager对象的正确初始状态。


But note that the "double checked lock" example is only correct for Java 5 and later. 但是请注意,“双重检查的锁”示例仅适用于Java 5及更高版本。


If there are multiple threads sharing the GameManager instance, it is most likely necessary to do other things to get the application to (always) behave correctly. 如果有多个线程共享GameManager实例,则很可能有必要做其他事情以使应用程序(始终)正确运行。

The first implementation is more safely than other one implementation in multiple processor.Following rule guarantee why first implementation is safely. 第一个实现比多处理器中的其他实现更安全。遵循规则可以保证第一个实现是安全的。

Because the JLS 17.4.5 Happens-before Order define one rule: 因为JLS 17.4.5 Happens-before Order定义了一个规则:

The default initialization of any object happens-before any other actions (other than default-writes) of a program. 任何对象的默认初始化都发生在程序的其他任何动作(默认写操作除外)之前。

so the instance field is initialized fully when someone call the method get(). 因此,当有人调用方法get()时, instance字段将完全初始化。

The second implementation is called lazy initialization ,but it is not correctly in your code.In order to staring program faster,using the lazy initialization is a good practice if the initialization of GameManager spend more time. 第二种实现称为lazy initialization ,但是在您的代码中不正确。为了更快地lazy initialization程序,如果GameManager的初始化花费更多时间,则使用lazy initialization是一个好习惯。

With second implementation,there is two approach which can make the GameManager thread safe. 通过第二种实现,有两种方法可以使GameManager线程安全。

The first,you can make the method instance synchronized,just like this: 首先,您可以使方法instance同步,如下所示:

public synchronized static GameManager instance(){
 if(_instance == null) _instance = new GameManager();
 return _instance;
}

But it is not high performance,because every call will be synchronized with that method in multiple thread. 但这不是高性能,因为每个调用都会在多线程中与该方法同步。

The second,you can use double-check in the method instance , and declare the _instance as volatile ,the following code: 第二,您可以在方法instance使用double-check并将_instance声明为volatile ,以下代码:

static volatile GameManager _instance;

public static GameManager instance(){
    if (_instance== null) {              //0
        synchronized(GameManager.class) {  //1
            if (_instance == null)          //2
                _instance= new GameManager ();  //3
        }
    }
    return o;
}

This implementation code is correctly and high performance. 此实现代码正确且高性能。

Why the field _instance should be volatile?Let's see the following situation with no volatile ,there is two thread called thread1 and thread2 ,and they all call the method instance()`: 为什么field _instance should be volatile?Let's see the following situation with no volatile ,there is two thread called thread1 and thread2 ,and they all call the method instance() ,and they all call the method

  1. thread1 run at point 0 in above code 线程1在上面的代码中的点0运行
  2. thread1 check the _instance is null and no one acquire the lock,so it can run at point 3 thread1检查_instance为null并且没有人获得锁,因此它可以在第3点运行
  3. with the code _instance= new GameManager (); //3 使用代码_instance= new GameManager (); //3 _instance= new GameManager (); //3 ,because of the instruction reorder in Java Memory Model ,the JVM can break up the instruction order in 'interpreter or jit .Let's see what happened with that code.The instance has been constructed,the constructing instance is just be allocated memory(like the instruction new in JVM ,not in JAVA ),and then assign to the field _instance`,but notice the instance is not initialize completely(for example,not initialize field with default value,or call static construct method); _instance= new GameManager (); //3 ,由于Java Memory Model中的指令重新排序, JVM可以在“解释器” or jit”中分解指令顺序。 .Let's see what happened with that code.The instance has been constructed,the constructing instance is just be allocated memory(like the instructionin JVM ,not in JAVA ),and then assign to the field _instance`,但注意到实例未完全初始化(例如,不与默认值初始化字段,或调用静态构建方法);
  4. The thread2 run at point 0,and check the _instance is not null,so it will return the _instance which is not initialize fully,and the instance is not trustworthy. 线程2在点0运行,并检查_instance不为null,因此它将返回未完全初始化的_instance ,并且该实例不可信。

If the _instance is volatile ,the volatile can guarantee the _instance initialize fully when someone read it.See the 17.4.5 Happens-before Order ,there is one rule: 如果_instancevolatile ,那么volatile可以保证_instance在有人读取它时完全初始化。请参见17.4.5 Happens-before Order ,有一条规则:

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.t 在随后每次对该字段进行读取之前,都会写入一个易失字段(第8.3.1.4节)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM