- 关键词:性能
-
是什么:
CPU运行速度、CPU从内存读写数据、IO设备从磁盘读写数据到内存,3者存在巨大的速度差异。CPU执行一天,相当于CPU从内存读写数据一年;读写内存一天,相当于IO设备读写十年。
性能往往是由最短的木板决定的,基于此现状,计算机体系结构、操作系统、编译软件分别作了以下优化:
1) 设置缓存,CPU读写缓存快于读写内存,可以提升CPU的使用率;这导致了可见性问题
2) 设置多进程、多线程,分时复用CPU,均衡CPU和IO设备速度差异(这里不明白,是均衡CPU和内存还可以理解,还需要进一步深入);这引入了线程切换,导致了原子性问题
3) 对代码进行重排序,增加缓存利用率,减少对内存、磁盘的读取;这导致了有序性问题; -
缓存导致了可见性问题:
课程中给出的例子是这个,感觉容易和原子性混淆,故而采用了另外的一个例子:
/**
* 可见性测试
* 测试0:
* 测试方法test0:对于变量count使用volatile和不使用volatile的区别
*
* 测试1:
* 测试方法test1:使用volatile不能保证原子性
*
* 测试2:
* 测试方法test2:使用synchronized能保证原子性
*/
public class M1C1Test0 {
static volatile int count = 300;
public static void main(String[] args){
// test0();
// test1();
test2();
}
/**
* 测试0:对于变量count使用volatile和不使用volatile的区别
*/
public static void test0(){
new Thread(()->sonThreadFunction()).start();
/**
* 这里如果不sleep的话,该例子就不生效了,原因还未知
*/
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
count = 1000;
System.out.println("已置 count = " + count);
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("已置 count = " + count + ",又过了5秒");
}
public static void sonThreadFunction(){
System.out.println("执行开始");
while (count < 500){
}
System.out.println("执行完成");
}
/**
* 测试1:使用synchronized能保证原子性
*/
public static void test2() {
Thread thread0 = new Thread(()->add100K0());
thread0.start();
add100K0();
try {
thread0.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正确值为" + 200300);
System.out.println("实际值为" + count);
}
public static synchronized void add100K0(){
for(int i = 0; i < 100000; i++){
count ++;
}
}
/**
* 测试2:使用volatile不能保证原子性
*/
public static void test1() {
Thread thread0 = new Thread(()->add100K());
thread0.start();
add100K();
try {
thread0.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正确值为" + 200300);
System.out.println("实际值为" + count);
}
public static void add100K(){
for(int i = 0; i < 100000; i++){
count ++;
}
}
}
1) test0的输出结果,线程A和线程B各自有独立的CPU缓存,值都是先从CPU缓存中读取,如果不设置volatile,在某些情况下(比如CPU缓存不够用)再将CPU缓存中的值flush到内存,不会马上写到内存,所以导致count置为1000后,对另外一个线程来说仍不可见。
参照:java中关于volatile的理解疑问?:
count没有volatile:
执行开始
已置 count = 1000
已置 count = 1000,又过了5秒
count有volatile:
执行开始
执行完成
已置 count = 1000
已置 count = 1000,又过了5秒
2)测试2和测试3参照这个都能明白:java中关于volatile的理解疑问?:
- 线程切换导致了原子性问题,在上面的例子中也有说。
4.编译优化带来的有序性问题