CPU工作原理:
- 单核CPU工作原理-高速缓存:
(1)cpu读取数据时按照L1,L2,L3,物理内存的顺序依次查找
(2)L1,L2,L3的速度依次递减,容量依次递增
2.多核CPU工作原理-高速缓存:
并发编程问题:
- 缓存一致性问题:多核多线程并行执行导致L1,L2缓存数据不一致
1.举个例子:
结果分析:
编译器优化和指令重排:
上述1属于编译器重排序,2,3属于处理器重排序。重排序会导致多线程程序出现内存可见性问题。
举个例子-重排序:执行完成后 i = 1或者0
public class CpuReOrder { private int a = 0; private boolean flag = false; public void write(){ a = 1; //1 flag = true; //2 } public void read(){ if(flag){ //3 int i = a; //4 System.out.println(i); } } public static void main(String[] args){ final CpuReOrder cpuReOrder = new CpuReOrder(); //写线程 new Thread(()->cpuReOrder.write()).start(); //读线程 new Thread(()->cpuReOrder.read()).start(); } }
java内存模型
- 重排序-数据依赖性:
2.As-if-serial:
并发编程的问题:
- 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
(1)x=10; y=x; x++; x=x+1;x=new x(); 哪些操作具有原子性?
2.可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。(volatile,锁)
3.有序性即程序执行的顺序按照代码的先后顺序执行。(happens-before)
原子性、可见性、有序性是一个抽象的概念。其底层问题就是前面提到的硬件层面的缓存一致性问题、处理器优化问题和指令重排问题。
缓存一致性问题其实就是可见性问题。而处理器优化是可以导致原子性问题的(指令优化执行)。指令重排即会导致有序性问题。
举个例子:执行结束后:count=?
内存模型
1.算机内存模型:为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。它解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。
2.内存屏障:每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。
(1)硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。内存屏障有两个作用:阻止屏障两侧的指令重排序;强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
(2)对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存加载数据;对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存, 让其他线程可见。
3.**java内存屏障:**java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是Load,store两种的组合,完成一系列的屏障和数据同步功能。
(1)LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
(2)StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
(3)LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
(4)StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。 它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
4.数据竞争:
5.顺序一致性内存模型:
6.顺序一致性模型中程序执行示意图
正确同步的程序:
JMM与顺序一致性模型执行效果对比:
7.**happens before规则:**在JMM中要保证一个操作的结果对另一个操作可见,那么这两个操作(可能是多个线程)之间要存在happens-before关系。
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens- before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)
8.happens-before 具体规则:
(1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续的操作。
(2)监视器锁的规则:锁的解锁happens-before随后对它的加锁。
(3)volatile变量的规则:对一个volatile域的写happens-before于任意后续对它的读。
(4)传递性:A happens-before B, B happens-before C,那么A happens-before C。
(5)start()规则:线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happens-before 于B线程的任意操作。这意味着:线程A在执行ThreadB.start()之前对共享变量所做的修改,在线程B执行后都将对B可见。
(6)join()规则:线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before 于线程A从ThreadB.join()操作成功返回。这意味着:线程A执行操作ThreadB.join()并成功返回后,线程B的任意操作都将对线程A可见。
举个例子:
9.Volatile内存语义总结:
10.Volatile内存语义实现-JMM针对编译器指定的重排序规则表
11.Volatile内存语义实现
12.锁的释放获取建立的happens-before原则
13.JMM的内存可见性保证