1.前言
1.1多核并发缓存架构
cpu缓存模型
1.2Java内存模型
java线程内存模型跟CPU缓存模型类似,是基于CPU缓存模型建立起来的,java线程内存模型是标准化的,屏蔽掉了底层不同计算机的区别。跨平台!!!!
线程间不能直接交互的,必须通过主内存进行通信。volatile关键字就保证了多个线程共享变量的一致性。
2.内存模型的理解
2.1JMM数据原子操作
- read(读取):从主内存读取数据
- load(载入):将主内存读取到的数据写入工作内存
- use(使用):从工作内存读取数据来计算
- assign(赋值):将计算好的值重新赋值到工作内存中
- store(存储):将工作内存数据写入主内存
- write(写入):将store过去的变量值赋值给主内存中的变量
- lock(锁定):将主内存变量加锁,标志为线程独占状态
- unlock(解锁):将主内存变量解锁
2.2原子操作过程
例子:static volatile initFlag =false;
2.3volatile底层实现原理
早期做法:
总线加锁(性能低):
CPU从主内存读取数据到高速缓存,会在总线对这个数据加锁,这样其他CPU没法去读或写这个数据,直到这个CPU使用完数据释放锁之后其他CPU才能读取该数据
问题:性能低
volatile底层的做法:
MESI缓存一致性协议:
多个CPU从主内存读取同一个数据到各自的高速缓存,当其中某个CPU修改了缓存里的数据,该数据会马上同步回主存,其他CPU通过总线嗅探机制可以感知到数据的变化从而将自己缓存例的数据失效。
问题:如果修改后的值通过总线后还没来得及对主内存中进行修改时,其他线程已经嗅探到了改变立即将对应数据失效并从主内存中重新读取,可读到的还是原来的值。 这里有个细节就是当修改后的数据通过总线时会对这个数据加锁lock,在主内存修改完值后解锁unlock.
注意:早期总线加锁是read数据时就加锁了,write之后在解锁;而MESI缓存一致性协议只有在修改同步数据的一瞬间加锁!!加锁是逃不掉的,但可以尽可能的把加锁的力度减小!!
volatile缓存可见性实现原理
底层实现主要通过汇编lock前缀指令,它会锁定这块内存区域的缓存并回写到主内存,此操作被称为“缓存锁定”,MESI缓存一致性协议机制会阻止同时修改被两个以上处理器缓存的内存区域数据。一个处理器的缓存值通过总线回写到内存会导致其他处理器相应的缓存失效。
2.4并发编程三大特性:可见性,原子性,有序性
volatile保证可见性与有序性,但是不保证原子性,保证原子性需要借助synchronized这样的锁机制
例:下面代码打印的结果是小于10000的(volatile保证了可见性但并不是立即的可见性,因为中间还有其他的原子操作)
public class volatileTest{
public static volatile int num=0;
public static void increase(){
num++;
}
public static void main(String[] args) throw InterruptedException{
Thread[] threads = new Thread[10];
for(int i=0;i<threads.length;i++){
threads[i]=new Thread(new Runnable(){
@Override
public void run(){
for(int i=0;i<1000;i++){
increase();
}
}
});
}
for(Thread t : threads){
t.join();
}
System.out.println(num);
}
}
例:下面代码有可能出现a=0,b=0,a=1,b=0,a=0,b=1,a=1,b=1的情况。
因为JVM和CPU有一个指令优化,会根据既有的优化逻辑把代码进行指令重排;不希望出现这种情况加上volatile关键字就可以解决。为两个变量加了volatile后,在CPU做指令重排优化代码时是不会优化volaile变量赋值的指令码的,会为这些变量赋值操作的前后加上内存屏障。
public class volateSerialTest{
static int x=0,y=0;
public static void main(String[] args) throws InterruptedException{
Set<String> resultSet =new HashSet<>()l
Map<String,Integer> resultMap =new HashMap<>();
for(int i=0;i<1000000;i++){
x=0;y=0;
resultMap.clear();
Thread one = new Thread( new Runnable(){
public void run(){
int a=y;
x=1;
resultMap.put("a",a);
}
});
THread other =new Thread(new Runnable(){
public void run(){
int b=x;
y=1;
reasultMap.put("b",b);
}
});
one.start();
other.start();
ont.join();
other.join();
resultSet.add("a="+resultMap.get("a")+","+"b="+resultMap.get("b"));
System.out.println(resultSet);
}
}
}