在前一部分(从单例模式谈起(一))我们讨论单例模式时,谈到了 Double-Checked Locking
问题。我们讲, volatile
关键字并不能解决Double-Checked Locking 问题。这一节我们讨论与此相关的问题。
现代编译器为了提高程序的效率而对代码做了很多优化。这些优化大部分是有益的,但是也为多线程编程带来问题。
寄存器缓存
我们来看如下代码:
1 2 3 4 5 6 7 8 9 10 11 |
x = 0; Thread1 { lock(); x++; unlock(); } Thread2 { lock(); x++; unlock(); } |
x++
的值有锁保护,所以它是独占的,x++ 的行为不会被并发破坏。那么 x 的值必然为 2。 然而现实中并非一定如此:编译器有可能为了提高 x 的访问速度而将 x 放入线程的某个寄存器中,而线程的寄存器是线程私有的。 如果 Thread1 先获得了锁,那么程序的执行过程有可能是这样的:
- [Thread1] 读取 x 到寄存器 R1. (R1 = x = 0)
- [Thread1] R1++. unlock. 由于之后可能还会访问 x, 所以 Thread1 不将 R1 写回 x . (x = 0)
- [Thread2] 读取 x 到寄存器 R2 (R2 = x = 0)
- [Thread2] R2++
- [Thread2] 将 R2 写回 x (x = R2 =1)
- [Thread1] 在某个时候将 R1 写回 x (x = R1 = 1)
由此可见,即使正确的加了锁,也不能保证多线程安全。