Does Java have the static order initialisation fia

2019-04-21 05:32发布

A recent question here had the following code (well, similar to this) to implement a singleton without synchronisation.

public class Singleton {
    private Singleton() {}
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Now, I think understand what this is doing. Since the instance is static final, it's built long before any threads will call getInstance() so there's no real need for synchronisation.

Synchronisation would be needed only if two threads tried to call getInstance() at the same time (and that method did construction on first call rather than at "static final" time).

My question is therefore basically: why then would you ever prefer lazy construction of the singleton with something like:

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

My only thoughts were that using the static final method may introduce sequencing issue as in the C++ static initialisation order fiasco.

First off, does Java actually have this problem? I know order within a class is fully specified but does it somehow guarantee consistent order between classes (such as with a class loader)?

Secondly, if the order is consistent, why would the lazy construction option ever be advantageous?

11条回答
Animai°情兽
2楼-- · 2019-04-21 06:34

Just a little note about the first implementation: the interesting thing here is that class initialization is used to replace classical synchronization.

Class initialization is very well defined in that no code can ever get access to anything of the class unless it is fully initialized (i.e. all static initializer code has run). And since an already loaded class can be accessed with about zero overhead, this restricts the "synchronization" overhead to those cases where there is an actual check to be done (i.e. "is the class loaded/initialized yet?").

One drawback of using the class loading mechanism is that it can be hard to debug when it breaks. If, for some reason, the Singleton constructor throws an exception, then the first caller to getInstance() will get that exception (wrapped in another one).

The second caller however will never see the root cause of the problem (he will simply get a NoClassDefFoundError). So if the first caller somehow ignores the problem, then you'll never be able to find out what exactly went wrong.

If you use simply synchronization, then the second called will just try to instantiate the Singleton again and will probably run into the same problem (or even succeed!).

查看更多
不美不萌又怎样
3楼-- · 2019-04-21 06:34

The code in the first version is the correct and best way to safely lazily construct a singleton. The Java Memory Model guarantees that INSTANCE will:

  • Only be initialized when first actually used (ie lazy), because classes are loaded only when first used
  • Be constructed exactly once so it's completely thread-safe, because all static initialization is guaranteed to be completed before the class is available for use

Version 1 is an excellent pattern to follow.

EDITED
Version 2 is thread safe, but a little bit expensive and more importantly, severely limits concurrency/throughput

查看更多
We Are One
4楼-- · 2019-04-21 06:35

In Effective Java, Joshua Bloch notes that "This idiom … exploits the guarantee that a class will not be initialized until it is used [JLS, 12.4.1]."

查看更多
等我变得足够好
5楼-- · 2019-04-21 06:36

You initialize eagerly because you don't have to write a synchronized block or method. This is mainly because synchronization is generally considered expensive

查看更多
不美不萌又怎样
6楼-- · 2019-04-21 06:37

I'm not into your code snippet, but I have an answer for your question. Yes, Java has an initialization order fiasco. I came across it with mutually dependent enums. An example would look like:

enum A {
  A1(B.B1);
  private final B b;
  A(B b) { this.b = b; }
  B getB() { return b; }
}

enum B {
  B1(A.A1);
  private final A a;
  B(A a) { this.a = a; }
  A getA() { return a; }
}

The key is that B.B1 must exist when creating instance A.A1. And to create A.A1 B.B1 must exist.

My real-life use case was bit more complicated - the relationship between the enums was in fact parent-child so one enum was returning reference to its parent, but the second array of its children. The children were private static fields of the enum. The interesting thing is that while developing on Windows everything was working fine, but in production—which is Solaris—the members of the child array were null. The array had the proper size but its elements were null because they were not available when the array was instantiated.

So I ended up with the synchronized initialization on the first call. :-)

查看更多
登录 后发表回答