typora-root-url: ./
CPU多核并发缓存架构
JMM(Java线程内存模型)底层实现原理
基于CPU缓存模型建立的,屏蔽掉了底层不同计算机的区别。
所有的共享变量都存储在主内存。每条线程还有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
JMM数据原子操作
- read(读取):从主内存读取数据到工作内存中,以便load
- load(载入):将主内存读取到的数据写入工作内存
- use(使用):从工作内存读取数据来计算
- assign(赋值):将计算好的值重新赋值到工作内存中
- store(存储):将工作内存数据写入主内存
- write(写入):将store的变量值赋值给主内存中的变量
- lock(锁定):将主内存变量加锁,标识为线程独占状态
- unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量
深入汇编语言底层理解volatile关键字
共享变量改变,但是副本没有改变!
例子:Java
private static boolean initFlag = false;
public static void main(String[] args) {
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() {
System.out.println("prepareing data...");
initFlag = true;
System.out.println("======prepare data end...");
}
}).start();
}
JMM缓存不一致问题-修改:
private static volatile boolean initFlag = false;
System.out.println("======success"); //打印成功
简单理解为副本之间可以互相感知共享变量的改变,保证可变性。(线程之间通信)
volatile实现原理
总线加锁(性能太低)
CPU从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其他CPU没法去读或写这个数据,直到这个CPU使用完数据释放锁之后其他CPU才能读取该数据。
MESI缓存一致性协议
多个CPU从主内存读取同一个数据到各自的高速缓存,当其中某个CPU修改了缓存里的数据,该数据会马上同步回主内存,其它CPU通过总线嗅探机制可以感知到数据的变化从而将自己缓存里的数据失效。
volatile缓存可见性实现原理
底层实现主要是通过汇编lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定)并回写到主内存。
并发编程:可见性、原子性与有序性
有序性
for(int i = 0; i < 100000; i++) {
x = 0;
y = 0;
Thread one = new Thread(new Runnable() {
public void run() {
int a = y; // 3
x = 1; // 1
}
});
Thread other = new Thread(new Runnable() {
public void run() {
int b = x; // 4
y = 1; // 2
}
})
}
指令重排,导致程序问题。volatile!提示不要指令重排。
可见性
可见性,指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。
原子性
volatile保证可见性与有序性,但是不保证原子性,保证原子性需要借助synchronized这样的锁机制。
private static volatile int num = 0;
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for(Thread t : threads) {
t = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 1000; i++) {
increase(); // num++;
}
}
});
t.start();
}
for(Thread t : threads) {
t.join();
}
System.out.println(num); // <=10000
}
======字节码在这里=====
getstatic
iconst_1
iadd
putstatic
在自己加值的时候(iconst_1
或者iadd
),其他线程可能已经把值加上去了,所以自己加的是一个过期的数据。
总结
volatile的两个语义:
- 可见性
- 禁止指令重排序优化