一.主存区与cpu多级缓存模型
每个cpu都有自己的多级缓存,从主存读取数据以后缓存到自己的缓存区,这样读取性能能大大提升
下图表示cpu多级缓存
二.java内存模型
Java内存模型是跟cpu缓存模型是类似的,基于cpu缓存模型来建立的java内存模型,只不过java内存模型是标准化的,屏蔽掉底层不同的计算机的区别
read(从主存读取),load(将主存读取到的值写入工作内存),use(从工作内存读取数据来计算),assign(将计算好的值重新赋值到工作内存中),store(将工作内存数据写入主存),write(将store过去的变量值赋值给主存中的变量)
下图表示java内存模型
三.并发编程三大特性:可见性、原子性、有序性
(1)可见性:基于java内存模型,一个线程修改了某个变量,可能操作在自己的工作内存中,而其他线程读取的可能也是自己工作内存的值,或者读取主内没有被刷新的值,这里就是出现数据可见性的问题,线程1修改了某个变量,线程2不知道
(2)原子性:对于涉及共享变量访问的操作,若该操作从其执行线程以外的任意线程来看是不可分割的,那么该操作就是原子操作,相应的我们就称该操作是具有原子性的,就像i++,虽然是一句代码,但是实际是先读取i的值,再把i的值+1,这是是两个操作,两个线程同时执行i++,应该i++后为1,再i++后为2,这里可能两个都是1,所以这不是原子性的
(3)有序性:指令重排序,编译器和指令器,有的时候为了提高代码执行效率,会将指令重排序
四.MESI缓存一致性协议
看上面的内存模型,cpu嗅探机制,每个cpu都会对总线进行嗅探,如果线程1线程修改了某个变量,加了volatile的话,就会立马将数据刷到主内存,然后线程2就会嗅探到,然后让自己工作内存的数据失效,然后去主内存重新读取数据,解决数据可见性的问题,这就是数据MESI缓存一致性协议,至于MESI缓存一致性协议的实现细节,等后面volitale、synchronize、cas原理都分析完了以后再单独分析
五.happens-before原则
编译器、指令器可能对代码重排序,乱排,要守一定的规则,happens-before原则,只要符合happens-before的原则,那么就不能胡乱重排,如果不符合这些规则的话,那就可以自己排序
(1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
(2)锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
(3)volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
volatile变量写,再是读,必须保证是先写,再读
(4)传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
(5)线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
(6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
(7)线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
(8)对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
上面这8条原则的意思很显而易见,就是程序中的代码如果满足这个条件,就一定会按照这个规则来保证指令的顺序。
但是如果没满足上面的规则,那么就可能会出现指令重排,就这个意思。这8条原则是避免说出现乱七八糟扰乱秩序的指令重排,要求是这几个重要的场景下,比如是按照顺序来,但是8条规则之外,可以随意重排指令。
六.volitale的作用,和实现原理
通过上面的分析,我们知道在多线程编程的时候面临的三个问题,可见性、原子性、有序性,volitale可以解决可见性,和有序性问题,但是不能解决原子性问题
如何解决可见性问题:对volatile修饰的变量,执行写操作的话,JVM会发送一条lock前缀指令给CPU,CPU在计算完之后会立即将这个值写回主内存,同时因为有MESI缓存一致性协议,所以各个CPU都会对总线进行嗅探,自己本地缓存中的数据是否被别人修改,如果发现别人修改了某个缓存的数据,那么CPU就会将自己本地缓存的数据过期掉,然后这个CPU上执行的线程在读取那个变量的时候,就会从主内存重新加载最新的数据了
通过lock前缀指令+MESI缓存一致性协议实现
如何解决有序性的问题:内存屏障:禁止重排序,加了volatile的变量,可以保证前后的一些代码不会被指令重排
LoadLoad屏障:Load1;LoadLoad;Load2,确保Load1数据的装载先于Load2后所有装载指令,他的意思,Load1对应的代码和Load2对应的代码,是不能指令重排的
StoreStore屏障:Store1;StoreStore;Store2,确保Store1的数据一定刷回主存,对其他cpu可见,先于Store2以及后续指令
LoadStore屏障:Load1;LoadStore;Store2,确保Load1指令的数据装载,先于Store2以及后续指令
StoreLoad屏障:Store1;StoreLoad;Load2,确保Store1指令的数据一定刷回主存,对其他cpu可见,先于Load2以及后续指令的数据装载
对于volatile修改变量的读写操作,都会加入内存屏障
每个volatile写操作前面,加StoreStore屏障,禁止上面的普通写和他重排;每个volatile写操作后面,加StoreLoad屏障,禁止跟下面的volatile读/写重排
每个volatile读操作后面,加LoadLoad屏障,禁止下面的普通读和voaltile读重排;每个volatile读操作后面,加LoadStore屏障,禁止下面的普通写和volatile读重排
通过内存屏障:禁止重排序实现有序性问题
为什么不能实现原子性:通过上面的java内存模型,加了volitale,mesi缓存一致性协议也不能保证原子性问题,线程1修改了某个值里面,刷到了主存区,线程2使自己缓存的数据失效再去主内存读取数据,但是这个时候线程在计算的数据是不会改变的,还是可能拿旧的值计算,要实现原子性只能线程2不能计算,等主存区读到最新数据以后再开始计算才能实现原子性