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条回答
Rolldiameter
2楼-- · 2019-04-21 06:19

A class is initialized when it's accessed at runtime. So init order is pretty much the execution order.

"Access" here refers to limited actions specified in the spec. The next section talks about initialization.

What's going on in your first example is equivalently

public static Singleton getSingleton()
{
    synchronized( SingletonHolder.class )
    {
        if( ! inited (SingletonHolder.class) )
            init( SingletonHolder.class );
    } 
    return SingletonHolder.INSTANCE;
}

( Once initialized, the sync block becomes useless; JVM will optimize it off. )

Semantically, this is not different from the 2nd impl. This doesn't really outshine "double checked locking", because it is double checked locking.

Since it piggybacks on class init semantics, it only works for static instances. In general, lazy evaluation is not limited to static instances; imagine there's an instance per session.

查看更多
太酷不给撩
3楼-- · 2019-04-21 06:20

The patern that you described works for two reasons

  1. Class is loaded and initialized when first accessed (via SingletonHolder.INSTANCE here)
  2. Class loading and initialization is atomic in Java

So you do perform lazy initialization in a thread safe and efficient way. This pattern is better alternative to double lock (not working) solution to synchronized lazy init.

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

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.

No. SingletonHolder class will be loaded only when you invoke SingletonHolder.INSTANCE for the very first time. final Object will become visible to other threads only after it is fully constructed. Such lazy initialization is called Initialization on demand holder idiom.

查看更多
SAY GOODBYE
5楼-- · 2019-04-21 06:25

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)?

It does, but to a lesser degree than in C++:

  • If there is no dependency cycle, the static initialization occurs in the right order.

  • If there is a dependency cycle in the static initialization of a group of classes, then the order of initialization of the classes is indeterminate.

  • However, Java guarantees that default initialization of static fields (to null / zero / false) happens before any code gets to see the values of the fields. So a class can (in theory) be written to do the right thing irrespective of the initialization order.

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

Lazy initialization is useful in a number of situations:

  • When the initialization has side effects that you don't want to happen unless the object is actually going to be used.

  • When the initialization is expensive, and you don't want it to waste time doing it unnecessarily ... or you want more important things to happen sooner (e.g. displaying the UI).

  • When the initialization depends on some state that is not available at static initialization time. (Though you need to be careful with this, because the state might not be available when lazy initialization gets triggered either.)

You can also implement lazy initialization using a synchronized getInstance() method. It is easier to understand, though it makes the getInstance() fractionally slower.

查看更多
Emotional °昔
6楼-- · 2019-04-21 06:31

The only correct singletone in Java can be declared not by class, but by enum:

public enum Singleton{
   INST;
   ... all other stuff from the class, including the private constructor
}

The use is as:

Singleton reference1ToSingleton=Singleton.INST;    

All other ways do not exclude repeated instantiation through reflection or if the source of class is directly present in the app source. Enum excludes everything. ( The final clone method in Enum ensures that enum constants can never be cloned )

查看更多
太酷不给撩
7楼-- · 2019-04-21 06:34

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.

Not quite. It is built when the SingletonHolder class is initialized which happens the first time getInstance is called. The classloader has a separate locking mechanism, but after a class is loaded, no further locking is needed so this scheme does just enough locking to prevent multiple instantiation.

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)?

Java does have a problem where a class initialization cycle can lead to some class observing another class's static finals before they are initialized (technically before all static initializer blocks have run).

Consider

class A {
  static final int X = B.Y;
  // Call to Math.min defeats constant inlining
  static final int Y = Math.min(42, 43);
}

class B {
  static final int X = A.Y;
  static final int Y = Math.min(42, 43);
}

public class C {
  public static void main(String[] argv) {
    System.err.println("A.X=" + A.X + ", A.Y=" + A.Y);
    System.err.println("B.X=" + B.X + ", B.Y=" + B.Y);
  }
}

Running C prints

A.X=42, A.Y=42
B.X=0, B.Y=42

But in the idiom you posted, there is no cycle between the helper and the singleton so there is no reason to prefer lazy initialization.

查看更多
登录 后发表回答