📜 ⬆️ ⬇️

Function Pointer - forgotten implementation of the Singleton pattern

Many articles are written on how to properly implement the Singleton design pattern in Java.

As a rule, experts break spears around a problem, how to combine correct work in multi-threaded conditions and effective performance, ensuring a performance close to the maximum.

Personally, I think the only correct way to implement a Java singleton is the so-called Synchronized Accessor :
')
public class Singleton { private static Singleton instance; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 


This is how the authors of the Java Virtual Machine conceived the implementation of such a task; this is the implementation that is used in the standard Java class library. If for a program the method of access to a singleton becomes a bottleneck, then this is a reason for redesigning the program so that it doesn’t refer to the global object so often.

However, trying to refresh Java's capabilities of concurrency, I read old articles about variants of singletons and was surprised that I could not find a description of yet another way, which I call Function Pointer .

Function Pointer deserves attention by the fact that it is suitable not only for the Singleton template, but also for many other tasks. It can be used when there are several code branches in the program, one of which, starting from a certain point, becomes the only one. The remaining branches remain zombie code, which will never be executed. Just such a branch is present in most versions of singleton.

The idea is that we create an interface with one method and several implementations. Each branch is placed in a separate interface implementation. At the end of the method, we add the installation in the global variable (Function Pointer) of the implementation that will have to work out next time.

Here's what it looks like:

 public final class Singleton { private static FunctionPointer accessor = new SynchronizedFunction(); private Singleton() { System.err.printf("%s.%s()%n", Singleton.class.getName(), Singleton.class.getSimpleName()); } private static interface FunctionPointer { Singleton get(); } private static final class SynchronizedFunction implements FunctionPointer { @Override public synchronized Singleton get() { if (accessor != this) { return accessor.get(); } Singleton instance = new Singleton(); accessor = new DummyFunction(instance); return instance; } } private static final class DummyFunction implements FunctionPointer { private final Singleton instance; private DummyFunction(Singleton instance) { this.instance = instance; } @Override public Singleton get() { return instance; } } public static Singleton getInstance() { return accessor.get(); } } 


The first time Singleton.getInstance () is called, the control will receive a SynchronizedFunction object. His get () method is declared as synchronized, so for threads that call it, the happens-before relationship is fair. This ensures that the code Singleton instance = new Singleton() , will run exactly 1 time. Other threads included in SynchronizedFunction will see that the accessor field has already been changed, and will pass by the condition accessor != this .

All threads that will call Singleton.getInstance () after the end of SynchronizedFunction.get () will go over the link to the DummyFunction.get () method, which already does not contain any conditions or critical sections. This is simply an accessor method that always returns the same value.

We do not even need to declare volatile fields. If a thread does not see the entry in the accessor field, it will go to the synchronized method, where it will be required to see all updates made by other threads in this method.

Well, in the DummyFunction class, the only field is declared final. Java guarantees to us that final-fields are visible equally in all threads.

When compiling this code, Eclipse reports that the compiler performs access to fields with a private appearance in some non-standard, less efficient way and advises, for performance reasons, to increase the visibility of the accessor field and constructors. In this case, all these slow hits are made no more than 1 time, so the compiler tips can be ignored.

Often in implementations of the Singleton pattern, attention is paid to the possibility of handling errors that occur in the constructor of the target class. Let's make a constructor that works only from the 2nd time:

 private static final AtomicInteger cnt = new AtomicInteger(); private Singleton() { if (cnt.incrementAndGet() == 1) { throw new IllegalArgumentException(".   ."); } System.err.printf("%s.%s()%n", MySingleton.class.getName(), MySingleton.class.getSimpleName()); } 


It is easy to make sure that our implementation allows correctly catching and handling the exception thrown from the constructor. In case of an error, the static accessor field will not be changed, and our Singleton will remain in its original non-initialized state.

Let's see how effectively our Hotspot JVM class can compile. Run Java with the -XX: + UnlockDiagnosticVMOptions -XX: + PrintInlining options. We get the following diagnosis:

 @ 20 ru.habrahabr.Singleton::getInstance (9 bytes) inline (hot) @ 3 ru.habrahabr.Singleton$DummyFunction::get (5 bytes) accessor 


I do not really understand the messages of the JIT compiler, but in my opinion, this is the fastest that can be obtained from the getInstance () method. Instead, the accessor method is substituted, returning the value of one of the fields.

Let's sum up

+ 1 copy of class Singleton is guaranteed
+ Security for multithreaded use
+ Lazy initialization
+ Error Handling Designer
+ Effective implementation
- Calling a virtual method when retrieving a singleton instance
- Bulky code
- 3 additional classes: interface and 2 implementations

Links

[1] Singleton implementation in JAVA
[2] The correct singleton in java
[3] Singleton pattern
[4] How to implement a singleton pattern in Java?
[5] The "Double-Checked Locking is Broken" Declaration
[6] Singleton Design Pattern

Source: https://habr.com/ru/post/228079/


All Articles