Chinaunix首页 | 论坛 | 博客
  • 博客访问: 17416
  • 博文数量: 2
  • 博客积分: 116
  • 博客等级: 入伍新兵
  • 技术积分: 20
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-18 14:21
文章分类

全部博文(2)

文章存档

2011年(2)

我的朋友

分类: Java

2011-12-13 22:34:42

本文来自:

欢迎大家关注:

1. 前提基础
  • volatile Store 与 Store是不能被编译器重排
  • volatile Load 与 Load是不能被编译器重排

       关于上面这两点,可以参照和Intel的ia-32-architectures第3A卷的第8章。

2. 一个事例和一个推论和一个假设

一个例子:有两个volatile变量x, y,初始值均为0,有两个处理器1和2分别对x, y做读写操作,操作顺序如下:

处理器1 : Store x 1     |       Store y 1

处理器2 : Load y        |        Load x

因为对于处理器1,Store x 1和Store y 1不能重排,所以一定是Store x 1先;
同理,对于处理器2,Load y和Load x不能重排,所以一定是Load y先;

将四条操作针对x, y给出可能的操作顺序如下:

  • Store x 1, Store y 1, Load y, Load x  ==> x == 1, y == 1
  • Store x 1, Load y, Store y 1, Load x  ==> x == 1, y == 1
  • Store x 1, Load y, Load x, Store y 1  ==> x == 1, y == 0
  • Load y, Load x, Store x 1, Store y 1  ==> x == 0, y == 0
  • Load y, Store x 1, Load x, Store y 1  ==> x == 1, y == 0
  • Load y, Store x 1, Store y 1, Load x  ==> x == 1, y == 0

那么就有这样一个推论
当Load y发现y == 1时,则Load x的值一定也是1, 当x == 0时,y也一定是0.

因为y == 1,则说明Store y 1被执行,则Store x 1肯定也会被执行, 而当x == 0则,表示Load x 在 Store x 1之前,则Load y

也一定在Store y 1之前。

只针对x, 假设对y volatile变量的两步操作为原子操作的话,那么情况就有变了,只剩下这两种情况:

  • Store x 1, Store y 1, Load y, Load x  ==> x == 1, y == 1
  • Store x 1, Load y, Store y 1, Load x  ==> x == 1, y == 1
  • Store x 1, Load y, Load x, Store y 1  ==> x == 1, y == 0
  • Load y, Load x, Store x 1, Store y 1  ==> x == 0, y == 0
  • Load y, Store x 1, Load x, Store y 1  ==> x == 1, y == 0
  • Load y, Store x 1, Store y 1, Load x  ==> x == 1, y == 0

这个时候,就能保证Store x 1之后再Load x了

3. 一个巧妙的处理就有了现在Java版的volatile

有什么方法可以实现y的原子操作呢?
这个操作肯定是针对所有处理器而言的操作, 那么很容易想到锁总线的操作,如Lock前缀,
Lock前缀只适用于特定的一系列的指令,对于不适用的会抛出异常,具体大家很容就能找到,

曾经有人还利用这一点,做出很多巧妙的事情来,通过这个无用操作抛出异常,

可以hack到当时的内核地址信息等。

有点扯远了,在openJDK7的orderAccess_linux_x86.inline.hpp中,实现了这样的一个内联函数:

inline void OrderAccess::fence() {
  if (os::is_MP()) {
    // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
    __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
    __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  }
}

顾名思义,该内联函数类似与barrier的效果,及内存屏障,看

__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");

可以得到这样一些信息:

  •  volatile即表示禁止重排的
  •  汇编指令是一个写操作,结合这两点,即满足了上文的Store y 1类似的操作
  •  汇 编指令其实是一个无用的写操作,给%esp即栈顶+0,但因为lock前缀,使得它是原子的且锁总线的(不同情况的lock效果可能不同,这里暂且这样 说,不过至少它影响了全部的处理器),所以可以达到多处理器之间的因果关系,即相当于满足了Load y的完成操作,这样一来,自然就把Store x和Load x给错开了
  • 假如没有lock; addl $0, 0(%%esp),即为__asm__ volatile (“” : : : “cc”, “memory”);这也是内核针对编译器常用的屏障,即告知编译器,内存被修改了,不要再信任寄存器里的值了,所以越过该屏障后,编译器会重新从内存取 值,关于这一块,这里讲得很泛,因为本文的目的不在于此,网上关于内存屏障的讲解非常多
4. 结论

根据第3点得到的信息,其实可以这样理解,当两个处理器针对一个volatile做操作时,一个在Store而另一个在Load时,

因为fence()的lock使得Store和Load不得不错开,而Load自然会推迟到Store完成之后,所以才有了这个Happen-Before原则:

volatile变量的写先于volatile变量的读.

阅读(2161) | 评论(0) | 转发(0) |
0

上一篇:PHP入门经典书籍

下一篇:没有了

给主人留下些什么吧!~~