哎 得快点找到工作
https://www.bilibili.com/video/BV1Et411n7Ro?from=search&seid=15449624913687152734
CPU其实有缓存的,比如windows
而java的JMM模型其实也就类似于CPU缓存模型:
或者可以这么说,工作内存就是CPU的缓存,线程C其实就是运行在CPU的,主内存就是第一张图的RAM
看如下代码:
package com.company;
public class Main {
public static boolean initFlag = false;
public static void main(String[] args) throws Exception {
// write your code here
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("waiting data...");
while(!initFlag) {
}
System.out.println("success");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
prepareData();
}
}).start();
}
public static void prepareData() {
System.out.println("prepare start");
initFlag = true;
System.out.println("prepare end");
}
}
运行结果就是,success永远不会打出来,因为两个线程用到的其实是initFlag的副本,而第一个线程的副本一直都是false,解决办法就是在initFlag前面加上volatile,volatile用来保证线程之间(共享)变量的可见性,上面代码其实就对应下面的操作
具体read,load,等命令的含义如下:
诸葛老师喜欢做总结,我也要多总结总结
针对上述JMM缓存不一致的问题,有两种方案
1.总线加锁
某个线程read的时候加锁lock,直到该线程write结束,才unlock,在这个过程中,其他线程无法read读取被lock的对象,直到对象被unlock,才能去read,这样自然能保持变量一致,但是性能很低
2.MESI 缓存一致性协议
多个CPU可读取同一个数据到高速缓存,当某一个CPU修改了缓存数据,会被立即同步到主内存,此时其他CPU通过CPU嗅探机制,可感知到数据变化,而将缓存数据失效
加了volatile的字段,汇编操作室会给他加一个lock指令前缀,其实也就是给该字段加锁,写回到主存以后,再unlock,但是volatile的锁粒度,相比总线锁,粒度大大减小。lock的目的是为了防止其他CPU读取原来无效的值(initFlag = false),因为有可能initFlag还没来得及变成true,其他线程就去因为缓存失效而重新去主存读initFlag了,所以必须lock住
lock指令的作用:
1. 会将当前处理器缓存行(工作内存)的数据立即写回到主内存
2. 写回操作会引起其他CPU缓存了该内存地址的数据无效
学东西容易忘记,那么就要多做笔记,多画脑图
并发编程三大特性
可见性、原子性、有序性
volatile可以保证可见性,有序性,但是不保证原子性
可见性,就是上面的CPU嗅探机制
有序性,就涉及到指令重排序,而加了volatile的字段,汇编不会对其进行重排序,也就保证了有序性,为什么不会进行重排序呢,因为和volatile字段相关的指令会加lock 前缀,而lock指令自带内存屏障,而cpu看到这个lock,指令重排就绝对不会把lock指令前面的代码排到lock后面去执行
内存屏障,happens-before和as-if-serial原则,具体还不知道