import java.util.Scanner;
public class Demo16 {
static class Counter{
public int count = 0;
}
//t1读数据,t2改数据
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(()->{
while(counter.count==0){
}
System.out.println("t1执行结束");
});
t1.start();
Thread t2 = new Thread(()->{
System.out.println("请输入一个int: ");
Scanner scnner = new Scanner(System.in);
counter.count = scnner.nextInt();
});
t2.start();
}
}
这段代码运行完会发现输入后线程没有结束。原因如下:
在t1中执行的while循环这个操作“counter.count == 0”在操作系统中分为两步:读内存(LOAD),比较(CMP);而while循环转的很快,这就意味着要多次进行LOAD和CMP。而比较可比读内存快多了,而且每次读的结果还是同一个。所以这时编译器就只执行一次load,后续CMP就不用重新读内存了(编译器优化)。而运行到t2的时候,count已经变成了我们输入的值,此时内存已经被修改了。而t1由于只读了一次内存所以并没有发现内存已经被修改。所以,进程结束不了。
为了解决问题,我们需要用关键字提醒编译器此处无需编译器优化。这就是volatile的作用。
volatile:用来修饰一个变量。此时被修饰的变量,编译器就不会做出“不读内存只读寄存器”这样的优化。
谈到volatile,就得先讲讲JMM(java Memory Model Java内存模型)了
volatile禁止了编译器优化,避免了直接读取CPU中寄存器(工作内存)中缓存的数据,而是每次都重新读内存(主内存)
ps:主内存才是真的内存。工作内存不是!他只是个存储区。
正常程序执行的过程中,会把主内存的数据,先加载到工作内存中,再进行计算处理。
编译器优化可能会导致不是每次都真的读取主内存,而是直接取工作内存中的缓存数据。(就可能导致内存可见性问题)
所以volatile 起到的效果, 就是保证每次读取内存都是真的从主内存重新读取~
修改后就对了
但是,volatile只能保证内存可见性而原子性不行。
public class Demo17 {
static class Counter{
volatile static public int count = 0;
public void increase(){
count++;
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Counter.count);
}
}
可以看到结果不是10000。(这段代码是在synchronized的博客中讲的,可以去看看啊ヾ(•ω•`)o)