1. 双重校验锁实现单例的问题

在延迟实现单例时,一般代码形式如下:

 public class Foo {
private static volatile Foo instance; public static Foo getInstance() {
//第一次检查,不锁定
if (null == instance) {
//一旦初始化,第一次检查将无法通过,不会有锁定开销
synchronized (Foo.class) {
//第二次检查,锁定
if (null == instance) {
instance = new Foo();
}
}
}
return instance;
}
}

看起来很简单,但这里有个容易忽略的点,就是instance变量,需要用volatile修饰。

为什么?如果不加的话会有什么问题呢?

让我们把目光聚焦到第11行,初始化instance变量。这一行代码可以分解为如下3行伪代码。

memory = allocate(); //1. 分配对象的内存空间

ctorInstance(memory);//2. 初始化对象

instance = memory; //3.设置instance指向刚分配的内存地址

上面伪代码中的2和3,是可能会被重排序的,重排序后将变成如下时序:

memory = allocate(); //1. 分配对象的内存空间

instance = memory; //3.设置instance指向刚分配的内存地址,注意,此时对象未初始化。 

ctorInstance(memory);//2. 初始化对象

那么在多线程并发的场景下,假设有两条线程AB同时访问这个方法,可能发生以下的访问顺序:

时间                            线程A                                                     线程B

1                          分配内存空间(对应1)

2               设置instance指向内存空间(对应3)

3                                                                                    判断instance是否为null

4                                                                                 由于instance不为null, 不再等待进行临界区,直接访问instance引用的对象

5                           初始化对象(对应2)

这样一来,B在访问instance变量时,可能由于instance未初始化而导致出现一些异常。

那么,为什么加上volatile修饰就可以避免这种情况呢?这就涉及到happens-before和volatile的语义了。

2.happens-before与volatile语义

从JDK5开始,Java使用新的JSR-133内存模型。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,以这两个操作之间必须要有happens-before关系。这里提到的两个操作,既可以是在一个线程之内,也可以是在不同的线程之间。

与程序员密切相关的happens-before规则如下。

1)程序顺序规则:一个线程中的每个操作,happens-before于该线程的任意后续操作。

2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。

3)volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

4)传递性:如果A happens-before B,B happens-before C,那么A happens-before C.

理解volatile特性的一个好方法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步。也就是说,对任意单个volatile变化的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

那么上面第三条规则是怎么实现的呢?请参考:深入理解Java内存模型(四)——volatile

简单来说,就是在volatile写之后加入了一个StoreLoad屏障,防止后面的读与前面的写重排序了。这样后面的线程读到的,就是一个完整的对象。

参考:《Java并发编程的艺术》

最新文章

  1. CSS 重设文章
  2. MySQL MEM_ROOT详细讲解
  3. Jquery获取selelct选中值
  4. 20.allegro.铺铜[原创]
  5. 分治法(一)(zt)
  6. 1055: [HAOI2008]玩具取名 - BZOJ
  7. HTTPS 服务器搭建
  8. selenium + python自动化测试环境搭建--亲测
  9. Unity调用Android的两个方式:其一、调用jar包
  10. 使用wamp扩展php时出现服务未启动的解决方法
  11. WPF DataGrid自定义样式
  12. JAVA反射之Class类的练习
  13. 分布式进阶(一)Windows 7下硬盘安装Ubuntu 14.04图文教程
  14. Python之字符串函数str()
  15. maven(一)
  16. python deamon(守护)线程的作用
  17. hdu 3534 树形dp ***
  18. ubuntu手动安装PhantomJS
  19. php中http_build_query函数
  20. 关于 Can't connect to MySQL server on 'localhost' (10061) 的一个解决方案

热门文章

  1. 03-22 Ajax验证用户登录
  2. Delphi 拖动
  3. Maven Profiles 定义不同环境的参数变量
  4. hadoop学习记录1 初始hadoop
  5. AOP的MethodBeforeAdvice
  6. hibernate事务配置Aop aop:advisor模式
  7. 常用sql语句备份
  8. shell编程——变量子串的常用操作
  9. listener does not currently know of SID given in connect descriptor
  10. ssh的发展历程与基本原理