深入分析Java单例模式的各种方案

发表于:2017-4-05 10:09

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:秋楓    来源:51Testing软件测试网采编

#
java
分享:
  单例模式
  Java内存模型的抽象示意图:
  所有单例模式都有一个共性,那就是这个类没有自己的状态。也就是说无论这个类有多少个实例,都是一样的;然后除此者外更重要的是,这个类如果有两个或两个以上的实例的话程序会产生错误。
  非线程安全的模式
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance() {
if (instance == null) //1:A线程执行
instance = new Singleton(); //2:B线程执行
return instance;
}
}
  普通加锁
public class SafeLazyInitialization {
private static Singleton instance;
public synchronized static Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}
  出于性能考虑,采用双重检查加锁的模式
  双重检查加锁模式
public class Singleton{
private static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(){
if(null == singleton){  //第一次检查
synchronized(Singleton.class){  //加锁
if(null == singleton){  //第二次检查
singleton = new Singleton();//问题的根源出在这里
}
}
}
return singleton;
}
}
  双重检查加锁模式相对于普通的单例和加锁模式而言,从性能和线程安全上来说都有很大的提升和保障。然而双重检查加锁模式也存在一些隐蔽不易被发现的问题。首先我们要明白在JVM创建新的对象时,主要要经过三个步骤。
  · 分配内存
  · 初始化构造器
  · 将对象指向分配的内存地址
  这样的顺序在双重加锁模式下是么有问题的,对象在初始化完成之后再把内存地址指向对象。
  问题的根源
  但是现代的JVM为了追求执行效率会针对字节码(编译器级别)以及指令和内存系统重排序(处理器重排序)进行调优,这样的话就有可能(注意是有可能)导致2和3的顺序是相反的,一旦出现这样的情况问题就来了。
  java源代码到最终实际执行的指令序列:
  前面的双重检查锁定示例代码的(instance = new Singleton();)创建一个对象。这一行代码可以分解为如下的三行伪代码:
  memory = allocate();   //1:分配对象的内存空间
  ctorInstance(memory);  //2:初始化对象
  instance = memory;     //3:设置instance指向刚分配的内存地址
  上面三行伪代码中的2和3之间,可能会被重排序(在一些JIT编译器上,这种重排序是真实发生的,详情见参考文献1的“Out-of-order writes”部分)。2和3之间重排序之后的执行时序如下:
  memory = allocate();   //1:分配对象的内存空间
  instance = memory;     //3:设置instance指向刚分配的内存地址
  //注意,此时对象还没有被初始化!
  ctorInstance(memory);  //2:初始化对象
  多线程并发执行的时候的情况:
  解决方案
  基于Volatile的解决方案
  先来说说Volatile这个关键字的含义:
  · 可以很好地解决可见性问题
  · 但不能确保原子性问题(通过 synchronized 进行解决)
  · 禁止指令的重排序(单例主要用到此JVM规范)
  Volatile 双重检查加锁模式
public class Singleton{
private volatile static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(){
if(null == singleton){
synchronized(Singleton.class){
if(null == singleton){
singleton = new Singleton();
}
}
}
return singleton;
}
}
  基于类初始化的解决方案
  利用静态内部类的方式来创建,因为静态属性由JVM确保第一次初始化时创建,因此也不用担心并发的问题出现。当初始化进行到一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。另外由于静态变量只初始化一次,所以singleton仍然是单例的。
  这个方案的实质是:允许“问题的根源”的三行伪代码中的2和3重排序,但不允许非构造线程(这里指线程B)“看到”这个重排序。
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号