Volatile

223 阅读3分钟

计算机基础

计算机的每条指令都是通过CPU来执行,而指令的执行由读取、写入组成~但是数据是存储在内存甚至磁盘中,CPU运行速度极快,如果每次都与物理存储交互,势必降低效率,因此有了CPU寄存器的概念。故先把数据从物理存储刷入CPU寄存器,然后CPU运行指令、计算,最后刷回物理存储

int x = x + 3;

多线程执行就会发现不是预期的结果,为了解决缓存不一致的问题 a.在总线加LOCK锁 b.换存一致性协议等

Instruction Reorder:CPU为了提高code的运行效率,会对code进行优化,其执行顺序可能与code的先后不一致~但是其保证结果与代码顺序执行的结果一致「根据指令间的数据依赖来保证结果一致」

JAVA原子性:一系列的操作,要么全部都执行,要都不执行;只有简单读取及将定值赋值给变量才是原子性的~变量间的相互赋值并不是原子性的;如下并非符合原子性

  1. CPU读取y值,进行++操作 2. 写入物理层中
String str = var;    y++ 

JAVA可见性:一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性

JAVA有序性:出于对性能的考虑,JVM并没有限制编译器对指令进行重排序。不会影响单线程程序的执行结果,却会影响多线程并发执行的正确性,JVM有自身有序性的保障,happens-before原则--->volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)

Volatile 关键字

public class ThreadTest {
    public static void main(String[] args) {
        ThreadTest tt = new ThreadTest();
        for (int i = 0; i< 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j< 10000; j++)
                        tt.incr();
                }
            }).start();
        }
        System.out.println(tt.x);
    }
    
    volatile int x = 0;
    
    public void incr() {
        x++;
    }
}

输出的结果与自己预期结果不一致?why

Volatile可以保障指令可见性、有序性~无法保障其原子性且自增也是非原子性的 详解:线程A读取共享变量x,而后该线程被阻塞;线程B读取x,进行incr操作,然后写回物理内存,此时x+1;而后线程A分到CPU时间片,因为其已经读取了x值,直接操作x+1即可;最终线程A、B两次调用incr实际上x的最终值只+1;有同学拿volatile原则来说事情,觉得以上解释不通,原因是线程A只是进行了读取并未进行写操作,所以无法影响线程B


//thread A
String str = "abc";     //1
sout(str)
[volatile] flag = false;  //2

str = "xyz";            //3
//thread B
public void setStr(String x) {
    this.str = x;
}
sout(str)

volatile 有序性及可见性,指令重排,语句1一定在语句2前执行,语句3一定在语句2之后执行,至于语句1、3他们自身的顺序是无法保证的~且语句1对语句3结果肯定是可见的~
线程B重置str时,会使得线程A寄存器中str变量失效,而后线程A重新读取str