开启掘金成长之旅**!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14天,点击查看活动详情**
编辑
目录
1.内存可见性问题-引入
构造一个myCounter类,成员flag,让t1线程中的循环条件为新创建的对象flag,让t2通过输入的整型值,控制flag的值,若非0,则t1循环应该终止!
class myCounter{
public int flag = 0;
}
public class ThreadDemo16 {
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 sc = new Scanner(System.in);
System.out.println("请输入一个整数:");
mycounter.flag = sc.nextInt();
});
t1.start();
t2.start();
}
}
我们先来看两个线程的状态:
编辑
t1线程是RUNNABLE状态
编辑
因为还未输入,t2其实还是阻塞状态
当我们输入一个非0值后:
编辑
编辑
此时t2线程已经执行结束,t1线程还在RUNNABLE状态!!
可以看到并没有打印"t1循环执行结束".我们预期的是t2改动了flag值,t1就应该结束循环了,但此时t1明显没有结束循环
这个问题就是"内存可见性问题"!因为这里的结果并不是我们预期的,所以也是一个Bug,这也是一个线程安全问题!
编辑
这里使用汇编指令理解,大致分为两步操作:
1.load,把内存中的flag的值读取到寄存器中
2.cmp,把寄存器的值和0进行比较,根据比较结果,决定下一步的跳转(这两步是一个循环,执行速度极快)循环执行次数这么多,t2真正修改flag之前,load得到的结果都是一样的,另一方面,load的操作和cmp操作相比,速度则非常慢!!(CPU针对寄存器的操作,要比内存操作快很多,快3
4个数量级,计算机对于内存的操作,比硬盘快34个数量级)
由于load执行的速度相对于cmp比较慢,再加上反复load的结果相同,JVM就做出了一个优化,不再重复load,判定没有人修改flag的值,于是只读一次就好了
不再重复load是编译器的优化 的一种方式,但是实际上是有人修改的,因此由于编译器的判断失误导致出现了bug
内存可见性问题
一个线程针对一个变量进行读取操作,另一个线程针对这个变量进行修改,此时线程读到的值,不一定是修改过后的值!!读线程没有感受到线程的改动!!归根结底还是编译器/JVM在多线程环境下优化时产生了误判!
此时就需要程序员手动干预了
2.volatile关键字
如何手动干预编译器/JVM的优化呢?
此时,给flag变量加上volatile 关键字就可以了,这个单词意思是可变的,容易失去的.它表示这个变量时"可变的",编译器每一次都要重新读取这个变量的内存内容,任意时间这个变量的值可能改变,编译器不能对它进行优化!!!
给flag变量加上volatile 关键字
编辑
结果
编辑
优化虽然能让速度提升起来,但是容易引发各种各样的问题!
这个关键字只能修饰变量,
不能修饰方法里的局部变量,局部变量出了方法就没了,只能在线程里边使用,不能多线程之间同时读取/修改,天然的规避了线程安全问题!
每个线程都有自己的"栈空间",方法内部的变量在"栈"这样的内存空间上(栈就是记录方法之间的调用关系),即使是同一个方法不同的线程调用,方法内的局部变量也在不同的栈空间中,本质上还是不同的变量,那么也不会涉及到多个线程读取/修改同一个变量的情况,不会出现内存可见性问题
上面的内存可见性问题也不是始终会出现,就是可能会误判,如果加个sleep,我们看结果
编辑
结果
编辑
这里结果正确了,sleep控制了循环的速度,编译器错误的优化也消失了,但是我们不知道编译器什么时候会优化,在应用程序方面无法感知,最稳妥的方法还是加上volatile
3.从java内存模型的角度内存可见性问题
java程序里,内存,每个线程还有自己的"CPU和寄存器"都是不同的,t1线程进行读取的时候,只是读取了t1线程的"CPU 寄存器"的值,t2线程进行修改的时候,先修改的是"CPU 寄存器"中的值,然后再把这个值同步到内存中,但是由于编译器优化,t1没有重新从内存中同步数据到"CPU 寄存器"中,读到的结果就是"修改之前的值"
主内存:main memory 贮存,也叫做内存
工作内存:work memory 工作存储区,不是内存,而是cpu上存储数据的单元(这里是存储器,还有其他东西(cache等))
这里的工作内存还不光指寄存器,还可能是高速缓冲器,cpu读取寄存器,速度比读取内存快太多了,为了减小这个差距,引入了cache,cache是指可以进行高速数据交换的存储器,它先于内存与CPU交换数据,因此速率很快
编辑
缓存的工作原理
当CPU要读取一个数据时,首先从CPU缓存中查找,找到就立即读取并送给CPU处理;没有找到,就从速率相对较慢的内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。正是这样的读取机制使CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在CPU缓存中,只有大约10%需要从内存读取。这大大节省了CPU直接读取内存的时间,也使CPU读取数据时基本无需等待。总的来说,CPU读取数据的顺序是先缓存后内存
相比于cpu和内存,它的存储空间居中,读写速度居中,成本居中,当CPU需要读到一个内存数据的时候,可能直接从内存读,也能从cache中的缓存,也可能读寄存器中的数据
因此硬件结构更复杂了,工作存储区=cpu寄存器+cpu cache,为了表述简单,直接就用"工作内存"代替了!
===