一个简单的单例示例
来源:互联网 发布:python pdf转html代码 编辑:程序博客网 时间:2024/06/06 02:32
一个简单的单例示例
单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写
public class UnsafeLazyInitiallization { private static UnsafeLazyInitiallization instance; private UnsafeLazyInitiallization() { } public static UnsafeLazyInitiallization getInstance(){ if(instance==null){ //1:A线程执行 instance=new UnsafeLazyInitiallization(); //2:B线程执行 } return instance; }}
上面代码大家应该都知道,所谓的线程不安全的懒汉单例写法。在UnsafeLazyInitiallization类中,假设A线程执行代码1的同时,B线程执行代码2,此时,线程A可能看到instance引用的对象还没有初始化。
你可能会说,线程不安全,我可以对getInstance()方法做同步处理保证安全啊,比如下面这样的写法
public class SafeLazyInitiallization { private static SafeLazyInitiallization instance; private SafeLazyInitiallization() { } public synchronized static SafeLazyInitiallization getInstance(){ if(instance==null){ instance=new SafeLazyInitiallization(); } return instance; } }
这样的写法是保证了线程安全,但是由于getInstance()方法做了同步处理,synchronized将导致性能开销。如getInstance()方法被多个线程频繁调用,将会导致程序执行性能的下降。反之,如果getInstance()方法不会被多个线程频繁的调用,那么这个方案将能够提供令人满意的性能。
那么,有没有更优雅的方案呢?前人的智慧是伟大的,在早期的JVM中,synchronized存在巨大的性能开销,因此,人们想出了一个“聪明”的技巧——双重检查锁定。人们通过双重检查锁定来降低同步的开销。下面来让我们看看
public class DoubleCheckedLocking { //1 private static DoubleCheckedLocking instance; //2 private DoubleCheckedLocking() { } public static DoubleCheckedLocking getInstance() { //3 if (instance == null) { //4:第一次检查 synchronized (DoubleCheckedLocking.class) { //5:加锁 if (instance == null) //6:第二次检查 instance = new DoubleCheckedLocking(); //7:问题的根源出在这里 } //8 } //9 return instance; //10 } //11}
如上面代码所示,如果第一次检查instance不为null,那么就不需要执行下面的加锁和初始化操作。因此,可以大幅降低synchronized带来的性能开销。双重检查锁定看起来似乎很完美,但这是一个错误的优化!为什么呢?在线程执行到第4行,代码读取到instance不为null时,instance引用的对象有可能还没有完成初始化。在第7行创建了一个对象,这行代码可以分解为如下的3行伪代码
memory=allocate(); //1:分配对象的内存空间ctorInstance(memory); //2:初始化对象instance=memory; //3:设置instance指向刚分配的内存地址
上面3行代码中的2和3之间,可能会被重排序(在一些JIT编译器上,这种重排序是真实发生的,如果不了解重排序,后文JMM会详细解释)。2和3之间重排序之后的执行时序如下
memory=allocate(); //1:分配对象的内存空间instance=memory; //3:设置instance指向刚分配的内存地址,注意此时对象还没有被初始化ctorInstance(memory); //2:初始化对象
回到示例代码第7行,如果发生重排序,另一个并发执行的线程B就有可能在第4行判断instance不为null。线程B接下来将访问instance所引用的对象,但此时这个对象可能还没有被A线程初始化。在知晓问题发生的根源之后,我们可以想出两个办法解决
- 不允许2和3重排序
- 允许2和3重排序,但不允许其他线程“看到”这个重排序
下面就介绍这两个解决方案的具体实现
基于volatile的解决方案
对于前面的基于双重检查锁定的方案,只需要做一点小的修改,就可以实现线程安全的延迟初始化。请看下面的示例代码
public class SafeDoubleCheckedLocking { private volatile static SafeDoubleCheckedLocking instance; private SafeDoubleCheckedLocking() { } public static SafeDoubleCheckedLocking getInstance() { if (instance == null) { synchronized (SafeDoubleCheckedLocking.class) { if (instance == null) instance = new SafeDoubleCheckedLocking();//instance为volatile,现在没问题了 } } return instance; }}
当声明对象的引用为volatile后,前面伪代码谈到的2和3之间的重排序,在多线程环境中将会被禁止。
- 一个简单的单例示例
- 一个简单的Java单例示例谈谈并发
- 从一个简单的Java单例示例谈谈并发
- 从一个简单的Java单例示例谈谈并发
- js通用的惰性单例示例
- SessionFactory单例示例代码
- 一个简单的单例测试
- 一个单例模式的简单例子
- 一个简单的单例模式
- 一个简单的单例设计
- 一个简单的单例模式
- 一个单例模式的简单例子
- 一个单例模式的简单例子
- 一个简单的单例模式
- 自动生成基于pyunit的接口测试用例示例
- 一个简单的单例模式 类的编写
- 使用VS2013 实现一个简单的单例模式singleton
- 简单的单例
- C语言记忆1
- Android root简单分析
- 简单理解与实验生成对抗网络GAN
- Java
- Gemcom Minex v6.03-ISO 1CD专为层状矿床设计的地质、采矿软件\
- 一个简单的单例示例
- ubuntu 15.04+java1.8+hadoop2.8.1
- UVa11280
- php 文件操作
- JZ2440_4_MMU
- reactNative 安卓failed install --(魅族)
- 分表分库
- HashMap原理
- java 上传文件的处理(MultipartFile file)