1 LanHanShi
1.1 Lazy, unsafe thread
This code is straightforward and uses lazy loading mode, but it has fatal problems. When multiple threads call getInstance() in parallel, multiple instances are created. That is to say, it will not work in multithreading
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
1.2 Lazy, thread safe
To solve the above problem, the easiest way is to set the entire getInstance() method to synchronized
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
It is thread-safe and solves the multi-instance problem, but it is not efficient. Because only one thread can call the getInstance() method at any time. But the synchronous operation is only needed for the first invocation, when the singleton object is first created. This brings us to the double check lock
2. Double check lock
The double Checked locking pattern is a method of locking with synchronous blocks. Programmers call this a double-checked lock because instance == NULL is checked twice, once outside and once inside the synchronized block. Why check again inside the synchronized block? Because it is possible for multiple threads to enter an IF outside the synchronized block, multiple instances will be generated if no second check is performed inside the synchronized block
public class Singleton{
private static Singleton instance;
private Singleton(a){}
public static Singleton getInstance(a){
if(instance == null) {// single check
synchornized(Singleton.class){
if(instance == null) {//double check
instance = newSingleton(); }}}returninstance; }}Copy the code
This code looks perfect, but unfortunately, it is flawed. In particular, instance = new Singleton(), which is not an atomic operation, actually does three things in the JVM. 1. Allocate memory for instance 2. Call the Singleton constructor to initialize member variable 3. Reference instance to the allocated memory space (instance is not null after this step)
But there is an optimization for instruction reordering in the JVM’s just-in-time compiler. That is to say, the order of step 2 and step 3 above is not guaranteed, and the final execution order may be 1-2-3 or 1-3-2. If it is the latter, instance is preempted by thread 2 before instance 3 is executed and instance 2 is already non-null (but not initialized), so thread 2 returns instance directly, uses it, and then naturally reports an error.
We simply declare the instance variable as volatile
public class Singleton{
private static volatile Singleton instance; // Declare as volatile
private Singleton(a){}
public static Singleton getInstance(a){
if(instance == null){
synchornized(Singleton.class);
if(instance == null){
instance = newSingleton(); }}returninstance; }}Copy the code
Some people argue that the reason for using volatile is visibility, that is, the ability to ensure that a thread does not have a local copy of an instance, instead fetching it from main memory each time. But that’s not true. The main reason for using volatile is another feature: it disallows instruction reordering optimizations. That is, there is a memory barrier (in the generated assembly code) behind assignments to volatile variables, and reads are not reordered to the barrier. For example, the fetch operation must be performed after 1-2-3 or 1-3-2. There is no case where the fetch operation is performed after 1-3. In terms of the “antecedent principle,” writes to a volatile variable occur first (in chronological order) before reads to it.
Note, however, that prior to Java 5, the use of volatile double-checking was problematic. The reason for this was that the JMM (Java Memory Model) prior to Java 5 was flawed, and even declaring variables volatile did not completely avoid reordering, primarily because the code before and after volatile variables still had reordering problems. The problem of volatile shielding reordering was only fixed in Java 5, so it was safe to use volatile after that.
You may not like this complicated and implicit approach, but there are better ways to implement thread-safe singleton patterns
3. Static final field
This approach is very simple because instances of singletons are declared as static and final variables and are initialized when the class is first loaded into memory, so creating the instance itself is thread-safe
public class Singleton{
// Class is initialized when it is loaded
private static final Singleton instance = new Singleton();
private Singleton(a){}
public static Singleton getInstance(a){
returninstance; }}Copy the code
If this were perfect, there would be no need to talk about double-checking. The disadvantage is that it is not a lazy initialization. The singleton is initialized as soon as the class is loaded, even if the client does not call the getInstance() method.
Hungry type way of creation in some scenes will not be able to use: such as the creation of a Singleton instance is depend on the parameters or configuration file, the getInstance () method must be called before a set parameters to it, as the single example of writing can’t use
4. Static inner classes are nested
I prefer to use the static inner class approach, which is recommended in Effective Java
public class Singleton{
private static class SingletonHolder(a){
private static final Singleton instance = new Singleton();
}
private Singleton(a){}
public static final Singleton getInstance(a){
returnSingletonHolder.instance; }}Copy the code
This approach still uses the JVM’s own mechanism to ensure thread-safety issues; Because SingletonHolder is private, there is no way to access it other than getInstance(), so it is lazy; There is no synchronization when reading instances at the same time, no performance defects; Nor does it depend on the JDK version
5. Enumeration Enum
It’s so easy to write singletons using enumerations! This is also its greatest strength. The following code is a common way to declare an enumeration instance
public class Singleton{
private Singleton(a){}
/** * Enumeration types are thread-safe and are loaded only once */
public enum SingletonEnum{
INSTANCE;
private final Singleton instance;
SingletonEnum(){
instance = new Singleton();
}
private Singleton getInstance(a){
returninstance; }}public static Singleton getInstance(a){
returnSingletonEnum.INSTANCE.getInstance(); }}Copy the code
We can access the INSTANCE via easysingleton.instance, which is much simpler than calling getInstance(). Creating enumerations is thread-safe by default, so you don’t need to worry about double Checked locking, and it also prevents deserialization from causing new objects to be recreated. But it’s still rare to see someone write like this, probably because they’re not familiar with it
conclusion
In general, there are five ways to write the singleton pattern: slacker, hungry, double-checked lock, static inner class, and enumeration. All of the above are thread-safe implementations, and the first method given at the beginning of this article is not the correct way to write it. As far as I’m concerned, in general it is good to use the hungry type directly, if clear requirement for lazy loading (lazy initialization) will tend to use a static inner class, if it involves deserialized object creation will try to use the way of enumeration to implement the singleton
Refer to the article: zhuanlan.zhihu.com/p/40716384