标签:之间 cat 看到了 解决方案 性能 基于 优化 问题 style
单例模式真是一个老掉牙的问题了,不过我今天是要说些里面更深点的知识,闲话少说,直接来代码
1、饿汉式
相信这种写法大家都知道,一开始接触单例的时候,大家应该都是用的这种方法:
package com.hd.single; public class Singleton { private Singleton(){} private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } }
这种方式优点就是线程安全, 缺点也很明显,就是类加载的时候,就已实例化该对象了,后面有可能用不到这个实例对象,这样就会造成空间浪费。因此就有了懒加载方式。
2、懒汉式
1)懒汉式L1
package com.hd.single; public class Singleton2 { private Singleton2(){} private static Singleton2 instance; public static Singleton2 getInstance(){ if(instance == null) //1 instance = new Singleton2(); //2 return instance; } }
这种懒汉式的优点和缺点也很明显,优点是按需加载,节省空间, 缺点是线程不安全。简单说就是,有可能线程A执行到“1”处时,阻塞住了,线程B抢到CPU,进来执行并实例化对象,然后线程A醒来后,继续往下执行,这样线程A和B取到的就是不同的对象。因此,又有了线程安全的版本。
package com.hd.single; public class Singleton2 { private Singleton2(){} private static Singleton2 instance; public static synchronized Singleton2 getInstance(){ if(instance == null) instance = new Singleton2(); return instance; } }
但是加了synchronized 之后会造成线程阻塞,影响性能。于是又提出了双检锁的方式
1 package com.hd.single; 2 3 public class Singleton2 { 4 5 private Singleton2(){} 6 private static Singleton2 instance; 7 8 public static Singleton2 getInstance(){ 9 if(instance == null){ 10 synchronized (Singleton2.class){ 11 if(instance == null){ 12 instance = new Singleton2(); 13 } 14 } 15 } 16 return instance; 17 } 18 }
看似双检锁的方式很完美,既解决了线程安全的问题,又兼顾了性能问题: 线程先判断instance变量是否为空,如果不为空,则直接返回。否则进入同步块去实例化对象。但事实这是一个错误的优化!
重点就是第12行代码(instance = new Singleton2();), 它创建了一个对象。这一行代码可以分解为如下的3行代码:
memory = allocate(); //1:分配对象的内存空间 ctorInstance(memory); //2:初始化对象 instance = memory; //3:设置instance指向刚分配的内存地址
上面2和3这两步之间,有可能会被重排序,2和3重排序之后的执行时序如下:
memory = allocate(); //1:分配对象的内存空间 instance = memory; //3:设置instance指向刚分配的内存地址 //注意此时对象还没有被初始化 ctorInstance(memory); //2:初始化对象
因此如果有线程A执行到3时,此时instance变量确实不为空,然后线程B判断instance不为空后返回,那么这是线程B 取到的就是一个空的对象。显示这样是有问题的,因此为了防止出现这个问题,需要使用volatile变量,来禁止指令重排序。
2)懒汉式 L2(基于volatile的解决方案)
package com.hd.single; public class Singleton { private Singleton(){} private volatile static Singleton instance; public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class){ if(instance == null){ instance = new Singleton3(); } } } return instance; } }
我们除了通过volatile的方式来禁止指令重排序,还可以提供另外一种思路:允许2和3重排序,但不允许其它线程“看到”这个重排序。 前面正是因为线程B看到了重排序,instance变量不为空,所以才造成其取到空的对象。
3)懒汉式L3(基于静态内部类的方案)
package com.hd.single; public class LazySingleton2 { private LazySingleton2() { } static class SingletonHolder { private static final LazySingleton2 instance = new LazySingleton2(); } public static LazySingleton2 getInstance() { return SingletonHolder.instance; } }
因为 在加载外部类时,其内部类不会同时被加载。只有调用 getInstance方法的时候,内部类才会去被加载,且只加载一次,不存在并发问题,因此是线程安全的。
另外,在getInstance()方法中没有使用synchronized关键字,因此没有造成多余的性能损耗。
本文给出了多个版本的单例模式,供我们在项目中使用。一般用L2,L3就基本够用。
标签:之间 cat 看到了 解决方案 性能 基于 优化 问题 style
原文地址:https://www.cnblogs.com/xiexin2015/p/9043429.html