Today we begin our journey into Java design patterns, starting with the singleton pattern.
You can check out my design Patterns column quotes, and follow my Design Patterns column: Preparing for Java Design Patterns
Without further ado, let’s get to the point
The singleton pattern
-
Singleton Design patterns are simple to understand. A class that allows only one object (or instance) to be created is a singleton. This design pattern is called the singleton design pattern, or singleton pattern for short.
-
In programming, we often encounter situations where we need to ensure that there is only one instance of a class, even if multiple threads are accessing it simultaneously, and we need to provide a global point of access to that instance. To summarize the theory, the singleton pattern mainly addresses a globally used class that is frequently created and destroyed to improve the overall performance of the code
-
You should see that you get the idea here, and the obscure text describing the singleton pattern is easier to understand. So let’s look at the classic singleton scenario:
-
For example, the database connection pool will not be created repeatedly, the generation and use of a singleton pattern Bean in Spring, the code needs to set some global properties and save.
This is the singleton pattern, so let’s use practical examples and code to give you a deeper understanding of the above description.
7+2 implementations of the singleton pattern
There are many ways to implement the singleton pattern, but it only has one purpose, and only creates one instance (object) forever. Let’s first elaborate seven common cases one by one.
Why are there two more? Right. Because we only have one purpose, to make sure that we only create one instance, so any way that satisfies that will do. I’ll write two ways that are less common, but I think you can use singletons.
1. Static classes
/** * singleton static class *@Date2021/8/1 5:43pm *@Author yn
*/
public class Singleton_01 {
public static Map<String,Object> cacheInfo = new ConcurrentHashMap<>();
}
public static void main(String[] args) {
Map<String,Object> map = Singleton_01.cacheInfo;
}
Copy the code
This static class approach is common in everyday business development. It is easier to initialize the Map class for global access when it is first run
2. Lazy mode (thread unsafe)
/** * Singleton lazy mode *@Date2021/8/1 5:43pm *@Author yn
*/
public class Singleton_02 {
private static Singleton_02 instance;
private Singleton_02(a){}// Get the object instance
public static Singleton_02 getInstance(a){
if (null! = instance){return instance;
}
// If empty, the internal new object
instance = new Singleton_02();
returninstance; }}// Get the object
public static void main(String[] args) {
Singleton_02 singleton_02 = Singleton_02.getInstance();
}
Copy the code
One particularly important feature of the singleton pattern is that it does not allow direct external creation, namely new Singleton_02 (), so the private property is added to the default constructor.
Consider: if multiple visitors obtain an object instance at the same time, will there be multiple instances of the same. The answer is yes. If the instance is not created (being created) by the first access, and a second thread request comes in, it goes to the new instance
3. Slacker Mode (thread safe)
Let’s do the following: add synchronized lock control
/** * singleton synchronized blessing *@Date2021/8/1 5:43pm *@Author yn
*/
public class Singleton_01 {
private static Singleton_01 instance;
private Singleton_01(a){}// Obtain object instance and synchronized control,
public static synchronized Singleton_01 getInstance(a){
if (null! = instance){return instance;
}
// If empty, the internal new object
instance = new Singleton_01();
returninstance; }}Copy the code
Synchronized ensures that only one thread can successfully lock the object at a time, and thus only new the object once.
This mode solves the problem of thread insecurity, but because locks are added to methods, all access needs to be locked, resulting in a waste of resources. It is not recommended to implement the singleton pattern in this way except in exceptional cases.
4. Han Hungry mode (thread safe)
/** * Han mode thread safety *@Date2021/8/1 5:43pm *@Author yn
*/
public class Singleton_03 {
private static Singleton_03 instance = new Singleton_03();
private Singleton_03(a){}// Get the object instance
public static Singleton_03 getInstance(a){
returninstance; }}Copy the code
This method is basically the same as the first instance of the Map at the beginning of the program, directly run the load when the program is started, and later when external need to obtain. This is not lazy loading, that is, whether or not the class is used in the program, it will be created at the beginning of the program.
Inner class of class (thread-safe)
/** * Singleton mode anonymous inner class *@Date2021/8/1 5:43pm *@Author yn
*/
public class Singleton_04 {
private static class singletonHolder {
private static Singleton_04 instance = new Singleton_04();
}
private Singleton_04(a){}// Get the object instance
public static Singleton_04 getInstance(a){
returnsingletonHolder.instance; }}Copy the code
The singleton pattern, implemented using the static inner class of the class, guarantees both thread-safety and slacker mode without compromising performance by locking.
This is mainly because the JVM virtual machine can guarantee the correctness of concurrent access by multiple threads, i.e. the constructor of a class can be loaded correctly in a multi-threaded environment. This is also a recommended singleton pattern.
The virtual machine ensures that the class constructor of a class is properly locked and synchronized in a multi-threaded environment. If multiple threads initialize a class at the same time, only one thread will execute the class constructor, and the other threads will block and wait until the active thread finishes executing the method.
So thread safe!!
6. Double lock check (thread safe)
/** * Double lock check (thread safe) *@Date2021/8/1 5:43pm *@Author yn
*/
public class Singleton_05 {
private static volatile Singleton_05 instance;
private Singleton_05(a){}// Get the object instance
public static Singleton_05 getInstance(a){
if (null! = instance){return instance;
}
synchronized (Singleton_05.class){
if (null == instance){
instance = newSingleton_05(); }}returninstance; }}Copy the code
This is a great way to make up for the lack of slob mode, without having to lock up every time. Double locking is an optimization of method level locking and reduces the time required to acquire an instance. This model can be used appropriately,
Enumeration singletons (thread safe)
/** * singleton mode enumeration *@Date2021/8/1 8:04pm *@Author yn
*/
public enum Singleton_06 {
INSTANCE;
public void testInstace(a){
System.out.println("I am an enumeration of singleton patterns --> -->"); }}Copy the code
This may be the least used. But this approach solves the primary thread safety, free serialization, and singleton issues. The calling method is as follows:
public static void main(String[] args) {
// Call the output
Singleton_06.INSTANCE.testInstace();
}
Copy the code
In contrast, you’ll find that enumerations implementing singletons are much simpler.
This approach, while functionally similar to the common domain approach, is more concise. Even in the face of complex serialization or reflection attacks, the serialization mechanism is provided free of charge to absolutely prevent this instantiation. Single element enumerations have become the best way to implement singletons.
It’s not that we don’t need to be thread-safe to use enumerations, it’s just that we don’t need to care about thread-safe guarantees. That is, there is a thread safety guarantee at the “bottom” level. This is where the JVM comes in, and you can get to the bottom of it by decompiling the enum classes, which I won’t go into here today.
OK, now that I’ve covered the classic seven ways to create singletons, here are two more ways to make singletons work.
Using CAS “AtomicReference” to implement singleton (thread safe)
/** * Use the cas idea to implement only one instance *@Date2021/8/1 5:43pm *@Author yn
*/
public class Singleton_07 {
// Use cas ideas to manage thread safety
private static final AtomicReference<Singleton_07> INSTANCE = new AtomicReference<Singleton_07>();
private Singleton_07(a){}// Get the object instance
public static final Singleton_07 getInstance(a){
for(;;) {// The AtomicReference get method is thread safe
// The cas idea is to query whether the current object exists each time
Singleton_07 instance = INSTANCE.get();
if (null! = instance)return instance;
INSTANCE.compareAndSet(null.new Singleton_01());
returnINSTANCE.get(); }}public static void main(String[] args) {
// A request
System.out.println(Singleton_07.getInstance());
// Make a second request
System.out.println(Singleton_07.getInstance());
// Class instance is the same}}Copy the code
The Java concurrency library provides a number of atomic classes that support concurrent access to data security, such as AtomicInteger, AtomicBoolean, AtomicLong, and AtomicReference. AtomicReference encapsulates a reference to an instance of V,
The singleton pattern above that supports concurrent access takes advantage of this feature. The advantage of using CAS is that it does not need to use traditional locking methods, but relies on CAS busy algorithms and the implementation of the underlying hardware to ensure thread safety. Compared to other lock implementations, there is no thread switching and blocking overhead, and it can support high concurrency. Of course, a disadvantage of CAS is that it is busy to wait, and if it has not been obtained, it will get stuck in an endless loop.
Implement singletons using ThreadLocal
If you have any questions about ThreadLocal, check out my history article: Dry stuff! ThreadLocal usage scenario
/** * Implement singletons with ThreadLocal, holding only one object instance *@Date 2021/8/1 8:30 下午
* @Author yn
*/
public class AppContext {
private static final ThreadLocal<AppContext> local = new ThreadLocal<>();
private Map<String,Object> data = new HashMap<>();
public Map<String, Object> getData(a) {
return getAppContext().data;
}
// Store data in batches
public void setData(Map<String, Object> data) {
getAppContext().data.putAll(data);
}
/ / data
public void set(String key, String value) {
getAppContext().data.put(key,value);
}
/ / get data
public void get(String key) {
getAppContext().data.get(key);
}
// The initialization method
private static AppContext init(a){
AppContext context = new AppContext();
local.set(context);
return context;
}
// Do delayed initialization
public static AppContext getAppContext(a){
AppContext context = local.get();
if (null == context) {
context = init();
}
return context;
}
// Delete the instance
public static void remove(a) { local.remove(); }}Copy the code
The above code implementation is essentially an extension of lazy initialization, except that a static object is replaced with a ThreadLocal to store a unique object instance. They chose ThreadLocal because it offers advantages over traditional thread synchronization.
In traditional synchronization, we usually lock the object to ensure that only one thread accesses the singleton class at a time. At this time, the class is shared by multiple threads. We all know that when to use synchronization mechanism, when to read and write the class, when to lock and release the object is very cumbersome requirements, which is relatively difficult for the general programmer to design and write.
ThreadLocal, on the other hand, provides a separate copy of the object for each thread, thus eliminating the problem of conflicting data access by multiple threads. Because each thread has its own copy of the object, synchronization between threads is eliminated.
So, most implementations of the singleton pattern today are basically using ThreadLocal implementations.
conclusion
Although the singleton pattern is only a very common pattern, but in a variety of implementation needs to use Java basic skills, including slacker mode, hunker mode, thread safety, static classes, inner classes, locking, serialization, and so on. In daily development, we have to choose one or the other depending on the actual situation.
Thank you for reading, creation is not easy, welcome to like, follow, forward, thank you, you can click on my profile picture to view the history of dry goods articles.
Design Patterns I’ll be updating this month, so stay tuned for the design Patterns column below, and I’ll keep you updated and see you next time!