volatile关键字的作用
volatile关键字的作用
并发编程有三大特性:原子性,有序性,可见性。
volatile能够保证可见性、有序性。volatile无法保证原子性。
可见性:
是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程不一定能够立即看得到修改的值。
可见性的引发原因是:
Java内存模型将所有的变量都存储在主内存中。而每个线程都有自己的工作内存,线程中会存有被该线程所使用的变量,这些变量在使用的时候会从主内存拷贝一份出来。线程对变量操作都在工作变量内完成。不同线程间无法直接访问对方的工作变量,线程间的变量传递通过主内存来完成。这就会涉及到一个线程数据同步的问题,例如有个全局变量i=0,线程A对变量i++,线程A会在自己的工作内存中执行i++操作,然后线程A将i的值写入到主内存中;在这时线程B也对i++,但是此时B中的i的值还是0,线程B在自己的工作内存中执行i++操作,然后线程B将i的值写入到主内存中。最后主内存中i的变量值为1,而不是我们预期的2。这个问题又称为缓存一致性问题。
有序性:
是指程序执行的顺序按照代码的先后顺序执行。
有序性引发的原因:
JVM在执行程序时,可能会进行指令重排序(Instruction Reorder)。所谓的指令重排序,是指处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致。指令重排序时,会根据指令间的依赖性来重排序,使整体的计算结果一致,例如:i++; a=i; 这两个步骤必然会保证i++先执行,而a=i后执行。但是在多线程环境下,例如:线程A doXXX(); flag = true; 线程B中根据flag的值来等待判断doXXX()执行,去做接下来的业务操作。 单从代码层面来看似乎是行得通的,但是事实上,falg=true并不一定代表着doXXX();已经执行完。由于doXXX()和flag=true没有数据依赖性,故有可能会被重排序。总的来说,指令重排序在单线程下是没问题的,但在多线程情况下,有可能会引发问题。
原子性:
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。试想一下,如果这2个操作不具备原子性,会造成什么样的后果。假如从账户A减去1000元之后,操作突然中止。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元。所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。在java并发编程中也有类似的问题。原子性问题的解决可以通过synchronized和Lock来实现。