专栏链接:多线程相关知识详解
编译器有优化功能,会对写好的代码进行优化,在多线程里面可能就会对代码的执行逻辑进行修改,就可能会产生bug
例如下面这个代码:
import java.util.Scanner;
class Counter{
public static int count = 0;
}
public class Demo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {//用lambda创建线程
while(Counter.count == 0){
}
System.out.println("t1执行结束");
});
t1.start();
Thread t2 = new Thread(() -> {
System.out.println("请输入一个整数:");
Scanner scanner = new Scanner(System.in);
Counter.count = scanner.nextInt();
});
t2.start();
}
}
运行结果:
程序并不会因为count的值的改变而终止线程
因为执行该操作的时候需要将内存里面的数据读取到CPU寄存器中,而寄存器和内存之间的访问速度相差3到4个数量级,而反复的从内存中读取数据的话就会导致程序的运行效率降低,所以编译器优化成第一次读取内存中的数据,然后保存在寄存器的缓存中,需要的时候再从缓存中读取数据,运行效率就会大幅度提高,但是运行结果正确比运行速度快更重要,因为数据都错了运行得再快也没用,所以该处应该要阻止编译器的优化
Ⅰ.调用Thread中的sleep方法
调用Thread中的sleep方法,让其进行休眠,下面用sleep使其休眠1秒,在没休眠的时候1秒内程序能够运行非常多次,对于程序的优化的需要就比较迫切,现在1秒只能运行一次,优不优化就不那么重要,1秒读取一次内存对于CPU来说轻而易举,所以每次就能从内存中读取到准确的数据
import java.util.Scanner;
class Counter{
public static int count = 0;
}
public class Demo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while(Counter.count == 0){
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t1执行结束");
});
t1.start();
Thread t2 = new Thread(() -> {
System.out.println("请输入一个整数:");
Scanner scanner = new Scanner(System.in);
Counter.count = scanner.nextInt();
});
t2.start();
}
}
运行结果:
Ⅱ.使用volatile
volatile用来修饰成员变量,能够在读取该数据的时候从内存中读取而不是从寄存器中读取,这就保证了数据的准确性
import java.util.Scanner;
class Counter{
public volatile static int count = 0;
}
public class Demo2 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while(Counter.count == 0){
}
System.out.println("t1执行结束");
});
t1.start();
Thread t2 = new Thread(() -> {
System.out.println("请输入一个整数:");
Scanner scanner = new Scanner(System.in);
Counter.count = scanner.nextInt();
});
t2.start();
}
}
运行结果:
volatile的两个作用:
①保证内存可见性问题.当一个线程进行修改同一个变量的时候,另一个线程能够及时的读取到改变量的值
②禁止指令重排序.编译时JVM编译器遵守内存屏障的限制,运行时靠屏障指令组织指令顺序
volatile与synchronized不一样,volatile并不保证原子性
volatile避免了直接读取CPU寄存器(工作内存)中缓存的数据,而是每次重新读取内存(主内存)
此次的工作内存不是真的内存,主内存才是真的内存