volatile关键字

来源:互联网 发布:hmmlearn python 教程 编辑:程序博客网 时间:2024/06/05 23:00

一 内存模型的基本概念

    计算机执行程序时,每条指令都由CPU执行,这个过程中涉及到内存中数据的读写。由于CPU的速度比内存快很多,为了提高程序的执行效率,CPU利用高速缓存存储数据,这相当于内存数据的一个副本。读取时,CPU从高速缓存中直接读取,修改时,先修改高速缓存,之后同步到主存中。

    在多线程的程序中,每个线程都有自己的CPU高速缓存。因此对于一个主存中的共享变量,它在不同的线程中存在多个副本。这样就存在缓存不一致的问题。


二 并发编程中的三大概念

    并发编程中有三个问题,原子性、可见性和有序性。保证这三个性质,才能实现线程安全。

1 原子性

    一个操作只有两个状态,未执行或者执行完毕, 不存在中间状态。

2 可见性

    当一个线程修改了某个共享变量的值后,其他使用到这个共享变量的线程,要立即看到修改后的值。(否则就会出现类似缓存不一致的问题)

3 有序性

    程序的执行顺序要按照代码的顺序执行。JVM为了提高程序的执行效率,有时会进行“代码重排序”,这种排序会保证程序的执行结果与排序前相同,但是仅仅是在单线程程序中适用。多线程程序可能会出现问题。


三 Java内存模型

1 所有变量都存储在主存中。

2 每个线程有自己的工作内存。

3 线程对变量的所有操作,都要在工作内存中进行,不能直接修改主内存。

4 线程不能修改其他线程的工作内存。


四 Java对原子性、可见性和有序性的保证

1 原子性

    Java内存模型只保证了基本数据类型的读取和赋值是原子性操作。如果希望实现更大范围的原子性操作,可以通过Synchronized或者Lock来实现。

2 可见性

    Java使用volatile关键字保证可见性。

    当一个变量被修饰为volatile时,他会保证修改的值立即被更新到主存中,当有其他线程需要读取时,他会去内存中读取最新的值。

    而普通变量被修改后,写回到主存的时间是不确定的,因此其他线程读取时,可能得到垃圾数据。

    使用synchronized和Lock也可保证可见性,在释放锁之前,线程对变量的修改值会被写入到主存中。

3 有序性(转自:http://www.importnew.com/18126.html)

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

在Java里面,可以通过volatile关键字来保证一定的“有序性”(具体原理在下一节讲述)。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。

下面就来具体介绍下happens-before原则(先行发生原则):

  • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  • 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
  • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
  • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

这8条原则摘自《深入理解Java虚拟机》。

这8条规则中,前4条规则是比较重要的,后4条规则都是显而易见的。

下面我们来解释一下前4条规则:

对于程序次序规则来说,我的理解就是一段程序代码的执行在单个线程中看起来是有序的。注意,虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”,这个应该是程序看起来执行的顺序是按照代码顺序执行的,因为虚拟机可能会对程序代码进行指令重排序。虽然进行重排序,但是最终执行的结果是与程序顺序执行的结果一致的,它只会对不存在数据依赖性的指令进行重排序。因此,在单个线程中,程序执行看起来是有序执行的,这一点要注意理解。事实上,这个规则是用来保证程序在单线程中执行结果的正确性,但无法保证程序在多线程中执行的正确性。

第二条规则也比较容易理解,也就是说无论在单线程中还是多线程中,同一个锁如果出于被锁定的状态,那么必须先对锁进行了释放操作,后面才能继续进行lock操作。

第三条规则是一条比较重要的规则,也是后文将要重点讲述的内容。直观地解释就是,如果一个线程先去写一个变量,然后一个线程去进行读取,那么写入操作肯定会先行发生于读操作。

第四条规则实际上就是体现happens-before原则具备传递性。


五 volatile关键字

1 volatile关键字对可见性的限制

(1)保证不同线程对于该变量的可见性

当一个线程对一个volatile变量进行修改时后,修改后的值会被立即写入到主存中,同时其他线程在“读取”对应变量时,工作内存中的变量值会被置为无效。因此,其他线程会重新读取最新的变量值。(可见性只是保证线程“读取”到最新的值)

2 volatile关键字对原子性的限制

volatile关键字不会对变量的操作增加原子性的限制。因此,在多个线程对同一个共享变量进行自增操作时,即使使用了volatile关键字,也无法保证最终结果的正确性。因为可能有多个线程读取了同一个正确的共享值,然后分别对该值进行加一操作,之后写会主内存。

3 volatile对有序性的限制

volatile修饰的变量,对其的读或者写操作,在代码重新排列时,其前面的操作不能排到它的后面,后面的操作不能排到它的前面。但是前面的多个操作可以重排,后面的操作可以重排。


4 因此,正是由于volatile无法保证原子性,因此,volatile是无法完全取代synchronized和lock,实现诸如原子类的同步功能。



参考地址:http://www.importnew.com/18126.html