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

java 内存可见性

2016-08-06 23:16 148 查看

可见性

可见性(visibility)的问题是Java多线程应用中的错误的根源。在一个单线程程序中,如果首先改变一个变量的值,再读取该变量的值的时候,所读取到的值就是上次写操作写入的值。也就是说前面操作的结果对后面的操作是肯定可见的。但是在多线程程序中,如果不使用一定的同步机制,就不能保证一个线程所写入的值对另外一个线程是可见的。造成这种情况的原因可能有下面几个:

CPU 内部的缓存

现在的CPU一般都拥有层次结构的几级缓存。CPU直接操作的是缓存中的数据,并在需要的时候把缓存中的数据与主存进行同步。因此在某些时刻,缓存中的数据与主存内的数据可能是不一致的。某个线程所执行的写入操作的新值可能当前还保存在CPU的缓存中,还没有被写回到主存中。这个时候,另外一个线程的读取操作读取的就还是主存中的旧值。

CPU的指令执行顺序

在某些时候,CPU可能改变指令的执行顺序。这有可能导致一个线程过早的看到另外一个线程的写入操作完成之后的新值。

编译器代码重排

出于性能优化的目的,编译器可能在编译的时候对生成的目标代码进行重新排列。

现实的情况

不同的CPU可能采用不同的架构,而这样的问题在多核处理器和多处理器系统中变得尤其复杂。而Java的目标是要实现“编写一次,到处运行”,因此就有必要对Java程序访问和操作主存的方式做出规范,以保证同样的程序在不同的CPU架构上的运行结果是一致的。Java内存模型(Java Memory Model)就是为了这个目的而引入的。JSR 133则进一步修正了之前的内存模型中存在的问题。总得来说,Java内存模型描述了程序中共享变量的关系以及在主存中写入和读取这些变量值的底层细节。Java内存模型定义了Java语言中的synchronized、volatile和final等关键词对主存中变量读写操作的意义。Java开发人员使用这些关键词来描述程序所期望的行为,而编译器和JVM负责保证生成的代码在运行时刻的行为符合内存模型的描述。比如对声明为volatile的变量来说,在读取之前,JVM会确保CPU中缓存的值首先会失效,重新从主存中进行读取;而写入之后,新的值会被马上写入到主存中。而synchronized和volatile关键词也会对编译器优化时候的代码重排带来额外的限制。比如编译器不能把 synchronized块中的代码移出来。对volatile变量的读写操作是不能与其它读写操作一块重新排列的。

Java 内存模型中一个重要的概念是定义了“在之前发生(happens-before)”的顺序。如果一个动作按照“在之前发生”的顺序发生在另外一个动作之前,那么前一个动作的结果在多线程的情况下对于后一个动作就是肯定可见的。最常见的“在之前发生”的顺序包括:对一个对象上的监视器的解锁操作肯定发生在下一个对同一个监视器的加锁操作之前;对声明为volatile的变量的写操作肯定发生在后续的读操作之前。有了“在之前发生”顺序,多线程程序在运行时刻的行为在关键部分上就是可预测的了。编译器和JVM会确保“在之前发生”顺序可以得到保证。比如下面的一个简单的方法:

public void increase() {
this.count++;
}


这是一个常见的计数器递增方法,this.count++实际是this.count = this.count + 1,由一个对变量this.count的读取操作和写入操作组成。如果在多线程情况下,两个线程执行这两个操作的顺序是不可预期的。如果 this.count的初始值是1,两个线程可能都读到了为1的值,然后先后把this.count的值设为2,从而产生错误。错误的原因在于其中一个线程对this.count的写入操作对另外一个线程是不可见的,另外一个线程不知道this.count的值已经发生了变化。如果在increase() 方法声明中加上synchronized关键词,那就在两个线程的操作之间强制定义了一个“在之前发生”顺序。一个线程需要首先获得当前对象上的锁才能执行,在它拥有锁的这段时间完成对this.count的写入操作。而另一个线程只有在当前线程释放了锁之后才能执行。这样的话,就保证了两个线程对 increase()方法的调用只能依次完成,保证了线程之间操作上的可见性。

如果一个变量的值可能被多个线程读取,又能被最少一个线程锁写入,同时这些读写操作之间并没有定义好的“在之前发生”的顺序的话,那么在这个变量上就存在数据竞争(data race)。数据竞争的存在是Java多线程应用中要解决的首要问题。解决的办法就是通过synchronized和volatile关键词来定义好“在之前发生”顺序。

参考:

http://www.infoq.com/cn/articles/cf-java-thread/

http://www.360doc.com/content/14/0521/11/7385274_379575711.shtml
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: