preface
The Java version of the singleton pattern was previously written, and recently supplemented with Kotlin’s notation, so it was issued together. 6+3 here is the same thing as Java 6+ Kotlin 3. For more design patterns, see AndroidNotes, which I wrote earlier.
Introduce a,
The definition ensures that a class has only one instance, and that it instantiates itself and makes that instance available to the entire system.
A simple example
public class Singleton {
private static Singleton instance = new Singleton;
private Singleton (a) {}public static Singleton getInstance(a) {
returninstance; }}Copy the code
Here is a simple example of using the singleton pattern. You can see that instances are indeed self-instantiated, using private constructors, so that instances of the class cannot be created using constructors anywhere else, and can only be obtained using the public getInstance() method.
Second, the Java version
2.1 Hungry (Thread-safe)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(a) {}public static Singleton getInstance(a) {
returninstance; }}Copy the code
Summary: Initializing an instance at class load time avoids thread-safety issues associated with multi-threaded concurrent operations, but wastes memory because the object is loaded into memory before it is used.
2.2 Lazy (Thread unsafe)
public class Singleton {
private static Singleton instance;
private Singleton(a) {}public static Singleton getInstance(a) {
if (instance == null) {
instance = new Singleton();
}
returninstance; }}Copy the code
Summary: the instance is initialized only when it is used for the first time, which solves the problem of memory waste caused by hungry, but it is not safe when multi-threading. For example, if thread A and thread B execute at the same time, then it is possible for both threads to execute until if (instance == null), thus creating two instances, so this method only applies to A single thread. For a detailed description of multithreaded concurrency, see what I wrote earlier to give you an easy understanding of threads, multithreading, and thread pools.
2.3 Lazy (Thread Safety)
public class Singleton {
private static Singleton instance;
private Singleton(a) {}public static synchronized Singleton getInstance(a) {
if (instance == null) {
instance = new Singleton();
}
returninstance; }}Copy the code
Summary: The second lazy thread-safety problem can be solved by adding a synchronization lock to the getInstance method so that multiple threads wait for the previous thread to finish executing before executing the current one.
2.4 Double Check Lock (Thread Safety)
public class Singleton {
private volatile static Singleton instance;
private Singleton(a) {}public static Singleton getInstance(a) {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = newSingleton(); }}}returninstance; }}Copy the code
If (instance == null) is added, and the lock is placed inside the method. The first nuller is added to prevent every thread from executing the lock.
However, instance = new Singleton(); Instead of an atomic operation, this line of code can be broken down into three steps:
- Allocate memory to instance
- Initialize instance by executing new Singleton();
- Point the instance object to the allocated memory space
And because of the instruction reordering nature of the JVM, there is no guarantee that the above three steps will be performed as 1>2>3, possibly 1>3>2. If (instance == null), thread B will return instance if (instance == null). If (instance == null) But because it never gets to step 2, instance is not empty but is not initialized, so calling an instance that is not initialized is definitely problematic.
That’s why the volatile keyword is used in the code because it takes care of instruction reordering, but only after JDK 1.5. The use of the volatile keyword also has performance implications.
2.5 Static Inner Classes (thread-safe)
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(a) {}public static Singleton getInstance(a) {
returnSingletonHolder.INSTANCE; }}Copy the code
Summary: This method does not initialize the instance when the Singleton class is loaded, but only when getInstance() is called for the first time, allowing lazy loading of the object. And the JVM can ensure that INSTANCE is instantiated only once. This approach provides the same effect as a double checklock, and eliminates the problem of using volatile only after JDK 1.5 and affecting performance. But this way of use is still relatively few people.
2.6 Enumeration (Thread Safety)
public enum Singleton {
INSTANCE;
public void testMethod(a) {}}Copy the code
Summary: Using enumerations is the perfect way to implement singletons. It prevents serialization and reflection from creating multiple instances, which the previous five methods do not solve by default. The only possible drawback to using enumerations is that they are not very readable. For the problem of serialization and reflection creating multiple instances, see why this article implements the singleton pattern with enumerations (to avoid reflection and serialization issues).
Third, Kotlin version
3.1 Lazy (Thread Safety)
Take a look at the official document:
object Singleton
Copy the code
You read that right, one line of code!
So which type of Java does it belong to? Here’s what the official document says:
Object declaration’s initialization is thread-safe and done at first access.
That is, initialization is thread-safe and only initialized the first time it is used. The Java equivalent of (3), (4), (5) is not only slacker but thread-safe.
So how does the inside work? Convert to Java code via Android Studio
public final class Singleton {
public static final Singleton INSTANCE;
private Singleton(a) {}static {
Singleton var0 = newSingleton(); INSTANCE = var0; }}Copy the code
You can see that it is indeed a singleton pattern, where instances are initialized using static code blocks.
So when is a static code block executed? As we know, the class loading process is divided into five phases: load, validate, prepare, parse, and initialize. Static code blocks are executed during the initialization phase. The JVM specifies the following conditions that trigger class initialization:
- When the VM starts, it initializes the main class that contains the main method.
- When an object instance is created through the new directive.
- When accessing a static method in a class.
- When accessing a static field in a class.
- When a reflection call is made to a class.
From the Java code transformed above, we know that when we use singletons, we get the INSTANCE through Singleton.instance, so we trigger the class initialization by accessing the static field in the class, which is when the static code block is executed. Moreover, the loading process of the class itself is thread-safe, so the singleton above is not only lazy but also thread-safe.
3.2 Enumeration (Thread safety)
enum class Singleton {
INSTANCE;
}
Copy the code
As with type (6) of the Java version, it prevents serialization and reflection from creating multiple instances.
3.3 with parameters
As you can see, neither can pass a parameter to a singleton through the static method getInstance() as Java does:
public static Singleton getInstance(Context context) {}
Copy the code
So we can imitate the official demo to write a:
class Singleton private constructor(context: Context) {
companion object {
@Volatile
private var instance: Singleton? = null
fun getInstance(context: Context)= instance ? : synchronized(this) { instance ? : Singleton(context).also { instance = it } } } }Copy the code
This is actually a conversion of type (4) of the Java version of “double check lock” to Kotlin’s notation.
Four, advantages and disadvantages
advantages
- Because the singleton pattern has only one instance in memory, it reduces memory overhead for objects that need to be frequently instantiated and then destroyed.
- Since there is only one instance of a singleton, creating resource-intensive objects can reduce the system’s performance overhead.
- Multiple occupancy of resources can be avoided.
- You can set up global access points on the system to optimize and share resource access.
disadvantages
- The singleton pattern generally does not have an interface, so it is difficult to extend, and can only modify the original code.
- The singleton pattern conflicts with the single responsibility principle.
5. Application scenarios
- Only one instance of an object is needed globally.
- Objects that need to be frequently instantiated and then destroyed.
- Create resource-intensive objects, such as accessing IO and database resources.
Six, how to choose?
- Java version: Each of the six approaches has its own pros and cons (thread safety, performance, code complexity, and readability), so you need to choose the right approach for your project. It is generally recommended to use the third thread-safe lazy mode (such as LocalBroadcastManager in Android), and the sixth enumeration mode if serialization and reflection are involved.
- Kotlin: Generally use the first method, use the second method of enumeration if serialization and reflection are involved, and use the third method if passing parameters is required.
About me
I am Wildma, CSDN certified blog expert, excellent author of Simple book programmer, good at screen adaptation. If the article is helpful to you, a “like” is the biggest recognition for me!