一、多核并发缓存架构
package cn.mrhan.java.thread;
/**
* @Author hanYu
* @Date 2021/5/30
* @Description
**/
public class VolatileVisibilityTest {
private static boolean initFlag = false;
public static void main(String[] args) throws InterruptedException {
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("prepareing data...");
initFlag = true;
System.out.println("prepare data end...");
}
}
点击运行
waiting data...
prepareing data...
prepare data end...
运行后发现 我们期望的=============success 并没有打印出来 这便是因为主线程的共享变量 被两个线程分别加载了两个共享变量副本 当第二个线程的共享变量副本的值改变后 第一个线程并没有感知到
此时可以加上volatile 关键字
waiting data...
prepareing data...
prepare data end...
=============success
底层交互模型图:
如何保证数据一致性问题:
1)总线加锁(会影响并行程序的效率),第二个线程执行完才会释放锁
2)现在:MESI缓存一致性协议
多个CPU从主内存读取同一个数据到各自的告诉缓存,当其中某个cpu修改了缓存里的数据,
该数据会马上同步回主内存,其他cpu通过总线嗅探机制可以感知到数据的变化 从而将自己缓存里的
数据失效
总线概念:
CPU和主内存是两个独立的组件,总线是实现两者数据的交互
cpu总线嗅探机制:监听
3.volatile缓存可见性实现原理
底层实现主要是通过汇编lock前缀指令 它回锁定这块内存区域的缓存并回写到主内存
会将当前处理器缓存行的数据立即写回到系统结存
这个写回内存的操作会引起其他CPU里缓存了改内存地址的数据无效
volatile由汇编语言实现
volatile加lock 和 总线加锁的区别 ?
总线加锁 涵括了整个线程的过程 而volatile加锁 只是在store 和write加锁 锁的粒度很小 可忽略不计
并发编程三大特征:可见性、原子性、有序性
volatile保证可见性、有序性 但是不能保证原子性
package cn.mrhan.java.thread;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.CountDownLatch;
/**
* @Author hanYu
* @Date 2021/7/30
* @Description
**/
public class VolatileAtomicTest2 {
public static volatile int num = 0;
public static void increase(){
num++;
}
public static void main(String[] args) throws InterruptedException {
ThreadPoolTaskExecutor poolTaskExecutor= new ThreadPoolTaskExecutor();
poolTaskExecutor.initialize();
poolTaskExecutor.setCorePoolSize(10);
poolTaskExecutor.setMaxPoolSize(30);
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i= 0;i<10;i++){
poolTaskExecutor.execute(new Runnable() {
@Override
public void run() {
for(int i =0;i<1000;i++){
increase();
}
countDownLatch.countDown();
System.out.println("countDownLatch:"+countDownLatch.getCount());
}
});
}
countDownLatch.await();
System.out.println(num);
}
}
预想运行结果是:10000 但是实际运行结果<=10000
为什么?
当两个线程同时对num进行赋值操作的时候,第一个线程对主内存进行存储、赋值的时候,由于缓存一致性协议,第二个线程会将工作内存中的num置为无效,此时重新读取主内存中的变量为1 之前进行+1的操作会丢失
为了保证原子性 可以加synchronized 关键字
public static synchronized void increase(){
num++;
}