先抛问题和我自己的理解:
问题一:MESI协议问题
比如这样一个场景:有一个变量a=0;有两个线程同时在多核环境下去执行a++去更改这个变量。 按照缓存一致性是不是应该是这样一个过程:两个cpu都把a读到缓存,此时a是一个S的状态,现在两个cpu都要去修改a,但是只能有一个cpu(假设是cpu1)把a修改为M状态,另一个会变为I(失效状态),当cpu2再要去执行a++的时候,会去主存读取a(因为缓存中的a已经是失效状态),这就会先触发cpu1缓存中的M状态的a(此时a=1)写回主存,然后cpu2才会读取到主存中a=1到缓存,这时两个cpu缓存中的a都变成S状态,然后cpu2再去做修改。---------过程如果是这样的话,最后输出的a一定是2了(测试结果并不是-。-)。我理解的这个过程有什么问题吗?
==============================分割线==========================================
问题二:引用地址 http://blog.csdn.net/javazejian/article/details/72772461(感谢博主辛苦整理)
publicclassDoubleCheckLock {
privatestatic DoubleCheckLock instance;
privateDoubleCheckLock(){}
publicstatic DoubleCheckLock getInstance(){
//第一次检测
if (instance==null){
//同步
synchronized (DoubleCheckLock.class){
if (instance == null){
//多线程环境下可能会出现问题的地方
instance =new DoubleCheckLock();
}
}
}
return instance;
}
}
上述代码一个经典的单例的双重检测的代码,这段代码在单线程环境下并没有什么问题,但如果在多线程环境下就可以出现线程安全问题。
原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。
因为instance = new DoubleCheckLock();
可以分为以下3步完成(伪代码)
- 1
- 2
- 3
- 1
- 2
- 3
由于步骤1和步骤2间可能会重排序,如下:
- 1
- 2
- 3
- 1
- 2
- 3
由于步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,
因此这种重排优化是允许的。但是指令重排只会保证串行语义的执行的一致性(单线程),
但并不会关心多线程间的语义一致性。
所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。
那么该如何解决呢,很简单,我们使用volatile禁止instance变量被执行指令重排优化即可。
- 1
- 2
- 1
- 2
================以下是我对问题二的理解,在原博客也和原博主讨论过==============
synchronized (DoubleCheckLock.class){
if (instance == null){
//多线程环境下可能会出现问题的地方
instance = new DoubleCheckLock();
}
}”已经加锁,锁可以保证原子性、可见性和顺序性,在锁释放以后(由于加内存屏障),instance会从缓存刷新到主存中,
失效其他cpu的instance缓存,其他线程进入到这个同步代码块的时候,判断instance 是否null时,instance 会从主存中获取。
synchronized能保证可见性和原子性了,在第一个线程程释放锁以后,instance = new DoubleCheckLock()的那三步已经执行完了,
这是由synchronized 的原子性保证的,而synchronized的可见性保证了其他线程再次获得锁的时候,instance 这个肯定不会为null。
所以也就不存在需要对instance 加volatile保证指令顺序性的问题了。退一步讲,synchronized 本身就能保证顺序性了。
---------------------------------------------问题OVER--------------------------------------
不知道这两个问题大家怎么看,欢迎留言讨论,谢谢