大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
volatile关键字和内存的可见性问题密切相关.
站前网站建设公司创新互联建站,站前网站设计制作,有大型网站制作公司丰富经验。已为站前超过千家提供企业网站建设服务。企业网站搭建\成都外贸网站建设要多少钱,请找那个售后服务好的站前做网站的公司定做!用一段代码解释什么为内存可见性问题:
class MyCounter {
public int flag = 0;
}
public class ThreadDemo15 {
public static void main(String[] args) {
MyCounter myCounter = new MyCounter();
Thread t1 = new Thread(() ->{
while (myCounter.flag == 0) {
// 这个循环体咱们就空着
}
System.out.println("t1 循环结束");
});
Thread t2 = new Thread(() ->{
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个整数: ");
myCounter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
}
在这段代码里:
t1要循环快速的重复读取,t2进行修改.
预期:t2把flag改成非0的值之后,t1随之就结束循环.
但是当我们把flag改成非0的值后,t1循环并没有随之结束.
同时可以看到,t2这个线程已经没了,执行完了,但是t1线程依然存在,还在继续循环.
这个情况就叫做"内存可见性问题".
这个代码是不是一个bug?
是,这也是一个线程不安全的问题,一个线程读,一个线程改.
这里使用汇编来理解,大概就是两步操作:
1.load,把内存中flag的值,读取到寄存器里.
2.cmp.把寄存器里的值,和0进行比较,根据比较的结果,来决定下一步往哪个地方来执行.(条件跳转指令).
上述是一个循环,这个循环执行的速度非常快,一秒执行百万次以上.
循环执行这么多次,在t2真正修改之前,load得到的结果都是一样的.另一方面,load操作和cmp操作相比,速度慢很多很多!!!(CPU针对寄存器的操作,要比内存快很多,3-4个数量级;计算机对于内存的操作,比硬盘快3-4个数量级).
由于load执行的速度太慢(相对于cmp操作来说),在加上反复load得到的结果都一样,所以JVM就对此做出了调整,不在真正重复的load,判定不在修改flag的值,所以只load一次就好了.(这也是编译器优化的一种方式).
总结内存可见性问题:一个线程针对一个变量进行读取操作,同时另一个线程针对这个变量进行修改.
此时读取到的值,不一定是修改过后的值.(进行读取操作的线程没有感知到变量的变化).
归根结底是编译器/JVM 在多线程环境下优化时产生了误判.
此时就需要程序员进行手动干预了.可以给flag这个变量加上volatile关键字.意思就是告诉编译器,这个变量是"异变"的,一定要每次都重新读这个变量的内存内容,不能再进行激进的优化了.
volatile关键字只能修饰变量,不能修饰方法.
可不可以修饰方法里的局部变量呢??
不可以 ,方法里的局部变量,只能再你当前线程里面使用.不能多线程同时读取/修改,天然的规避了线程安全问题.(只能再当前方法里使用出了方法变量就没了.方法内部的变量,再栈这样的空间上,每个线程都有自己的栈空间,即使是同一个方法,在多个线程中被调用,这里的局部变量也会处在不同的栈空间中,本质上是不同的变量)
上述的内存可见性问题,是编译器优化的问题,但也不是始终会出现的.(编译器可能存在误判,但也不是100%误判的)
比如将上述代码稍微调整一下
加了sleep控制了速度之后,即使不加volatile,代码也正确了.(刚才错误的优化也消失了)
编译器的优化,很多时候是不可预估的,应用程序这个角度是无法感知的.因此稳妥的做法,就是把改加volatile的地方都加上.
从JMM的角度表述内存可见性问题
JMM:Java Memory Model,Java内存模型.
表述:Java程序里,主内存,每个线程有自己的工作内存(t1和t2的工作内存不是同一个东西).
t1线程进行读取的时候,只是读取了工作内存的值.
t2线程进行修改的时候,先修改工作内存的值,然后再把工作内存的内容同步到主内存中.
但是由于编译器优化,导致t1没有重新的从主内存当中同步数据到工作内存,读到的结果就是修改之前的结果.
上述表述,来自于Java的官方文档.
解读:
主内存:main memory 主存,也就是咱们平时说的的内存.
工作内存:work memory 工作存储区,这个东西并非所说的内存,而是CPU上存储数据的单元(寄存器).
那为什么Java这里,不直接叫做"CPU寄存器",而是专门起了"工作内存"这样的说法?
这里的工作内存,不一定只是CPU的寄存器,还可能包括CPU的缓存cache.
CPU读取寄存器,速度比读取内快很多,因此就会在CPU内部引入缓存cache.
寄存器存储空间小,读写速度快,成本贵.
在内存和寄存器中间搞了个cache,存储空间居中,读写速度居中,成本居中.
内存存储空间大,读写速度慢,成本便宜(相对于寄存器来说).
当CPU要读取一个内存数据的时候,可能直接读内存,也可能是读cache,还能是读寄存器.
引入cache之后,硬件结构就变得复杂了.
工作内存(工作存储区):CPU寄存器+CPU的cache
一方面是为了表述简单,一方面也是为了避免涉及到硬件的细节和差异,Java这里就使用"工作内存"一词一言蔽之了.
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧