Simple definition
- For the singleton pattern, you are already familiar with the development students can not be familiar with it, is also widely used in various projects, no matter what high-level programming language is used, design pattern is always accompanied by it.
- Simply put, the singleton pattern ensures that there is only one instantiated object in a class, and then provides a global access point
Advantages and disadvantages of the singleton pattern
- Main advantages:
- Provides controlled access to a unique instance.
- Since only one object exists in system memory, system resources can be saved, and the singleton pattern can undoubtedly improve system performance for some objects that need to be created and destroyed frequently.
- A variable number of instances is allowed.
- Main disadvantages:
- Because there is no abstraction layer in the simple interest pattern, it is very difficult to extend singleton classes.
- Singleton classes are too heavy on responsibilities and violate the “single responsibility principle” to some extent.
- Abuse of singletons will bring some negative problems, for example, in order to save resources, the database connection pool object is designed as a singleton class, may lead to too many programs sharing the connection pool object and the connection pool overflow; If an instantiated object is not used for a long time, the system considers it garbage and collects it, resulting in a loss of object state.
Talk about how Java and Kotlin are implemented
Several singleton patterns in Kotlin
Hangry singleton
-
Hanchian singleton pattern is a simple way to implement singleton pattern. It has a feature that the singleton object will be instantiated whether it is needed or not.
-
Kotlin is very simple to implement, just define an object expression, no need to manually set the constructor private and provide global access points, which kotlin compiler does for you
object KSingleton : Serializable {// Implement the Serializable interface, which controls deserialization through the private, instantiated readResolve method
fun doSomething(a) {
println("do some thing")}private fun readResolve(a): Any {// Prevent the singleton from regenerating the object during deserialization
return KSingleton// Since the readResolve hook method is called when deserializing, you only need to return the current KSingleton object instead of creating a new one}}Copy the code
Thread-safe lazy singleton
- The singleton instance is created when the class is loaded, and initialized when we use it
class KLazilySingleton private constructor() : Serializable {
fun doSomething(a) {
println("do some thing")
}
companion object {
private var mInstance: KLazilySingleton? = null
get(a) {
returnfield ? : KLazilySingleton() }@JvmStatic
@Synchronized// Add a synchronized lock
fun getInstance(a): KLazilySingleton {
return requireNotNull(mInstance)
}
}
// Prevent the singleton from regenerating the object during deserialization
private fun readResolve(a): Any {
return KLazilySingleton.getInstance()
}
}
Copy the code
Double Check Lock (DCL) modified lazy singleton
-
The thread-safe singleton mode uses a synchronized lock directly to lock the getInstance method, which must be acquired each time the method is called. However, if the singleton is already initialized, there is no need to acquire the lock and simply return the singleton instance. Hence the DCL implementation singleton approach
-
In Kotlin, the singleton for thread-safe DCL is very, very simple, with only three lines of code, called the Companion Object + lazy proxy
class KLazilyDCLSingleton private constructor() : Serializable {//private constructor(
fun doSomething(a) {
println("do some thing")}private fun readResolve(a): Any {// Prevent the singleton from regenerating the object during deserialization
return instance
}
companion object {
// Using the @jvmStatic annotation, instance is called in Java as if it were a static function,
/ / similar KLazilyDCLSingleton. GetInstance (), if do not add annotations, in Java must call like this: KLazilyDCLSingleton.Com panion. GetInstance ().
@JvmStatic
// Use the lazy proxy and set LazyThreadSafetyMode to SYNCHRONIZED to ensure thread safety
val instance: KLazilyDCLSingleton by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { KLazilyDCLSingleton() }
}
}
Copy the code
Static inner class singleton
- Although DCL can solve problems such as resource consumption, redundant synchronized synchronization and thread safety to a certain extent, DCL failure still exists in some cases
- The singleton pattern of DCL is generally not recommended in multithreaded environments. So the static inner class singleton implementation is introduced
class KOptimizeSingleton private constructor() :Serializable {//private constructor(
companion object {
@JvmStatic
fun getInstance(a): KOptimizeSingleton {// Global access point
return SingletonHolder.mInstance
}
}
fun doSomething(a) {
println("do some thing")}private object SingletonHolder {// Static inner class
val mInstance: KOptimizeSingleton = KOptimizeSingleton()
}
private fun readResolve(a): Any {// Prevent the singleton from regenerating the object during deserialization
return SingletonHolder.mInstance
}
}
Copy the code
Enumerated the singleton
- Enumeration singletons are implemented to prevent deserialization, because we all know that enumeration class deserialization does not create new object instances
- The serialization mechanism for enumeration types guarantees that only existing instances of enumeration types are found, not new instances are created
enum class KEnumSingleton {
INSTANCE;
fun doSomeThing(a) {
println("do some thing")}}Copy the code
Several singleton patterns in Java
Lazy (thread unsafe)
// lazy singleton class. Instantiate yourself on the first call
public class Singleton {
// Private constructor
private Singleton(a) {}
// Private static variable
private static Singleton single=null;
// Exposed public static methods
public static Singleton getInstance(a) {
if (single == null) {
single = new Singleton();
}
returnsingle; }}Copy the code
- Generally speaking, the lazy style is divided into three parts: private constructors, private global static variables, and public static methods
- Play an important role in the static modifier is the static keyword, we know that in the program, any variables or code is at compile time by the system automatically allocates memory to store, and the so-called static is to point to in the compiled allocated memory will always exist, until the program exits memory will release the space, thus ensure the singleton class instance once created, It will not be reclaimed by the system unless it is manually set to NULL.
- The downside of this approach is thread-safety, which is solved by using the synchronized keyword, a second way of writing singleton.
Lazy (thread-safe)
public class Singleton {
// Private static variable
private static Singleton instance;
// Private constructor
private Singleton (a){};// Public synchronized static methods
public static synchronized Singleton getInstance(a) {
if (instance == null) {
instance = new Singleton();
}
returninstance; }}Copy the code
- This singleton implementation adds the synchronized keyword to the getInstance () method, which tells the Java (JVM) that getInstance is a synchronized method.
- Synchronization means that when two concurrent threads access the synchronized method in the same class, only one thread can be executed at a time, and the other thread must wait for the current thread to complete. Therefore, synchronization makes thread-safe, ensuring that there is only one instance of a singleton.
- The downside is that it synchronizes every time getInstance () is called, incurring unnecessary synchronization overhead. This mode is generally not recommended.
Hungry (thread-safe)
// The hunchman singleton class. When the class is initialized, it is instantiated by itself
public class Singleton {
// Static modified static variables are permanent once created in memory
private static Singleton instance = new Singleton();
private Singleton (a){}
public static Singleton getInstance(a) {
returninstance; }}Copy the code
- Hungry type has been created while class to create a static object for the use of system, won’t change, so naturally is thread-safe. Instance =new Singleton() can be written as:
static {
instance = new Singleton();
}
Copy the code
- It is also based on the classloder mechanism to avoid multithreading synchronization problems. Instance is instantiated when the class is loaded.
DCL double check mode
public class Singleton {
private static Singleton singleton; // Static variables
private Singleton (a){} // Private constructor
public static Singleton getInstance(a) {
if (singleton == null) { // Level 1 check
synchronized (Singleton.class) {
if (singleton == null) { // Layer 2 check
singleton = newSingleton(); }}}returnsingleton; }}Copy the code
- What makes this pattern special is the getInstance () method, where the Singleton is checked for empty twice, the first to avoid unnecessary synchronization and the second to create the instance in case of NULL.
Singleton = new singleton (); Statement, which looks like a line of code, but is not an atomic operation. This code will eventually be compiled into multiple assembly instructions, which do roughly three things:
- Allocate memory to the instance of Singleton
- Call the constructor of Singleton () to initialize the member field
- Point the Singleton object to the allocated memory space (i.e. the Singleton is not empty)
However, after JDK1.5, the volatile keyword was officially introduced and the singleton code was defined to address DCL failures.
private volatile static Singleton singleton; // Use volatile
Copy the code
Static inner class singleton pattern
public class Singleton {
private Singleton (a){};// Private constructor
public static final Singleton getInstance(a) {
return SingletonHolder.INSTANCE;
}
// Static inner class defined
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton(); // Where the instance is created}}Copy the code
- INSTANCE is not initialized when the Singleton class is loaded for the first time. INSTANCE is initialized only when the Singleton getInstance () method is called for the first time.
- Therefore, the first call to the getInstance () method causes the virtual machine to load the SingletonHolder class, which not only ensures the uniqueness of the singleton, but also delays the instantiation of the singleton.
Enumerated the singleton
The previous singleton implementations tend to be a little more cumbersome, or in some specific cases, something bad happens. The following is an implementation of the enumeration singleton pattern:
public enum Singleton { / / enum enumeration class
INSTANCE;
public void whateverMethod(a) {}}Copy the code
The biggest advantage of the enumeration singleton pattern is that it is simple to write. Enumerations in Java are the same as normal classes. Enumerations can have not only fields, but also methods. Enumeration singletons do not regenerate new instances even during deserialization. For the other methods, the following methods must be added:
private Object readResolve(a) throws ObjectStreamException{
return INSTANCE;
}
Copy the code
This ensures that no new methods are generated during deserialization
Use containers to implement the singleton pattern
public class SingletonManager {private static Map<String, Object> objMap = new HashMap<String,Object>();// Use HashMap as the cache container
private Singleton(a) {}public static void registerService(String key, Objectinstance) {if(! Objmap.containskey (key) {objmap.put (key, instance);// The first time is saved to Map}}public static ObjectgetService(String key) {return objMap.get(key) ;// Return the object corresponding to key}}Copy the code
- At the beginning of the program, multiple singletons are injected into a unified management class, and objects of the corresponding type are obtained according to the key when used.
Application scenarios
- So when should you consider using the singleton pattern?
- The system only needs one instance object, if the system requires a unique sequence number generator or resource manager, or if the resource consumption is too high to allow the creation of only one object.
- A single instance of a customer invoking class is allowed to use only one public access point, and the instance cannot be accessed by any other means than that public access point.
- Below we combine some Android source code to analyze the next
The EventBus framework commonly used in Android
- Can we see how singletons are used in EventBus, mainly with double-checked DCL
static volatile EventBus defaultInstance;
public static EventBus getDefault(a) {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = newEventBus(); }}}return defaultInstance;
}
Copy the code
This way it is very resource efficient, and the first time it is executed, the singleton is instantiated, but the first time it is loaded it is slower and acceptable
Singleton pattern implementation of LayouInflater
- Basic usage
LayoutInflater mInflater = LayoutInflater.from(this);
Copy the code
Get an example of LayoutInflater from LayoutInflater.
- Get the LayoutInflater service from LayoutInflater. From (context)
/** * Obtains the LayoutInflater from the given context. */
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
Copy the code
- To see how context.getSystemService works, the implementation of context is ContextImpl
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
Copy the code
- Enter the SystemServiceRegistry class
/** * Gets a system service from a given context. */
public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<? > fetcher = SYSTEM_SERVICE_FETCHERS.get(name);returnfetcher ! =null ? fetcher.getService(ctx) : null;
}
Copy the code
- At this point, I’m sure you already feel that this is the implementation of the singleton pattern using containers, by contrast, it is
private static finalHashMap<String, ServiceFetcher<? >> SYSTEM_SERVICE_FETCHERS =newHashMap<String, ServiceFetcher<? > > ();Copy the code
- Use map to store system services as key-value pairs. Injected when registerService is called.
/** * Statically registers a system service with the context. * This method must be called during static initialization only. */
private static <T> void registerService(String serviceName, Class
serviceClass, ServiceFetcher
serviceFetcher)
{
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
Copy the code
- We can also look at when these services are registered
static {
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
Copy the code
- Obviously, this is done in a static block of code to register the service, the first time the class is loaded, and only once to keep the instance unique
- From this process, it can be seen that the system stores the service in the form of key-value pairs in the HashMap. Users only need to obtain the specific service object when using it. When obtaining the specific object for the first time, getSystemService is called to obtain the specific object. RegisterService is called to cache the object in a list through the map, which can be fetched directly from the container the next time. Avoid creating objects repeatedly to achieve the singleton effect. Reduced resource consumption.
- In the Android source code, when the APP starts, the virtual machine first loads the class and registers various ServiceFetchers, such as LayoutInflater services. These services are stored as key-value pairs in a HashMap. Users only need to obtain the corresponding ServiceFetcher based on the key, and then obtain the specific service object through the getService function of the ServiceFetcher object. The creatService function of ServiceFetcher is called to create the service object for the first time, and the service object is cached in a list. The next time the service object is fetched from the cache, avoiding repeated object creation. The system core services in Android exist in the form of singletons, reducing resource consumption.
ImageLoader image loading framework
- The image loading framework ImageLoader instance is created using the singleton pattern. Because the ImageLoader contains thread pools, caching systems, and network requests, it is very resource-consuming and should not be used to create multiple objects
ImageLoader.getInstance();// Create a global instance in your Application.//getInstance() executes the source code
public static ImageLoader getInstance(a) {
if(instance == null) {// Double check DCL singleton mode
Class var0 = ImageLoader.class;
synchronized(ImageLoader.class) {// Synchronize code blocks
if(instance == null) {
instance = new ImageLoader();// Create a new instance}}}return instance;// Return an instance
}
Copy the code
Therefore, we can consider the singleton pattern if creating an object consumes too many resources
conclusion
The above are the analysis and summary of the singleton mode sorted out by my classmates in combination with the online materials. To put it simply, singleton mode is very common in the design mode. It is simple and simple at the same time, which is worth further discussion
- One core principle is private construction, and fetching an instance through static methods.
- In this process, thread safety must be guaranteed.
- Static internal implementation singletons or double-checking singletons with the Volatile keyword are recommended