您的位置:首页 > 编程语言 > Java开发

Java并发编程:volatile关键字解析(原子性问题)

2017-09-24 20:23 232 查看
博客园-海子:volatile关键字解:http://www.cnblogs.com/dolphin0520/p/3920373.html

在网上找到一篇非常非常好的JAVA并发编程的文章,彻底让我明白了volatile的作用,推荐大家过去观摩一下。

阅读该文章的时候有一个不太明白的地方,希望有人能解释一下,原文如下:

一、原文

volatile关键字保证了操作的可见性,但是volatile能保证对变量的操作是原子性吗?

public class Test {
public volatile int inc = 0;

public void increase() {
inc++;
}

public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}

while(Thread.activeCount()>1)  //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}


假如某个时刻变量inc的值为10,

  线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;

  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。

  然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。

  那么两个线程分别进行了一次自增操作后,inc只增加了1。

  解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面的happens-before规则中的volatile变量规则,但是要注意,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。

  根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。

二、问题

我的问题和原文评论中的4楼一样:

解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?

问题1:线程1读取10到工作内存,然后被阻塞;线程2读取10增加1,然后写入工作内存,然后写入主存;

问题来了,当线程1重新运行的时候它不会刷新自己的缓存吗?

您在4.1节中不是解释道“ 第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);”吗?

问题2:想确定的问题,当线程1将变量a读入到了自己的工作内存,还没有修改,被阻塞;线程2将主存中的a读入工作内存修改,写入主存;线程1苏醒是继续运行还是会检测工作内存的变量a是否需要刷新呢?

三、个人猜测

不懂底层,只能靠猜,理解可能不对,但是结果是对的不就行了,假装看懂了!

如原文所说,inc++并不是一个原子操作,这行代码编译成cpu可执行编码后会变成3个操作。

首先,执行指令前会将他们先

1. 读取inc 10

2. 10 + 1

3. 将11写入inc

线程在执行以上任意一个操作时,都可能因为某些原因阻塞。而我认为,只有操作1才会校验inc的缓存行是否有效。

知道原子性问题之后,那么我们来回答上面的问题:

问题1:当线程2将inc写入主存后,其实在线程1中变量inc的缓存行已经无效化了。

但是因为inc已经读取过了(操作1),正在进行自增操作(操作2),不需要再次读取缓存行,所以inc不会刷新。

问题2:不一定,这问题其实和问题1是一样的。

线程1苏醒后,a的缓存行已经无效化。

如果后续指令使用到变量a,会重新从主存中读取a,

如果关于a的相关指令在进行中,或者已经操作完毕,就不会重新访问a,不需要再次读取缓存行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: