一个简单的java内存模型图:
从图中可以看出,当是多核cpu时,每个cpu都有一个独立的高速缓存,多个线程会在多个cpu中独立执行
当对一个变量只是读操作的时候,jvm自作多情的做了一个优化,只是访问缓存的数据,只有进行写操作的时候,会刷新主内存的数据
执行如下操作:
i=1;
i=i+1;
cpu1:main memory-->i(1)-->cache(1)-->i+1-->cache-->i(2)-->main memory(2)
cpu2:main memory-->i(1)-->cache(1)-->i+1-->cache-->i(2)-->main memory(2)
在两个线程同时执行过程中会出现缓存不一致问题。
解决方案:
一、给数据总线加锁:
总线(数据总线、地址总线、控制总线)
LOCK#:虽然解决了数据一致问题,但会使得多核cpu串行化执行,效率变低
二、CPU高速缓存一致性协议(Intel的MESI):
核心思想:
1.当CPU写入数据时,如果发现该变量被共享(也就是说,在其他CPU中也存在该变量的副本),会发出一个信号,通知其他的CPU该变量的缓存无效
2.当其他的CPU访问该变量的时候,重新到主内存中获取
并发编程的三个重要概念:原子性、可见性、有序性
原子性:一个操作或者多个操作,要么都成功,要么都失败,中间不能有任何的因素影响终端
可见性:对同一个变量缓存一致性
有序性:有序性(顺序性),
jvm重排序只要求最终一致性,在单线程中可以保证最终一致性,多线程中不能保证
在java中是怎么保证这三个特性的?
1.原子性
对基本数据类型的变量读取和赋值是保证了原子性的,要么都成功,要么都失败,这些操作不可能被中断
a=10;原子性
b=a;不满足,1.read a;2.assign b;
c++;不满足,1.read c;2.add;3.assign c;
c=c+1;不满足,1.read c;2.add;3.assign c;
2.可见性
使用volatile关键字保证可见性
3.有序性
happens-before relationship
3.1 代码的执行顺序,编写在前面的发生在编写在后面的
-----------------Thread1-------------------
obj = createObj; 1
init = true; 2
-----------------Thread2-------------------
while(!init) { 1
sleep();
}
useObject 2
jvm在做优化的时候可能会对两个不相关的变量进行重排序,可能会使Thread1的1和2执行先后顺序改变,
从而影响Thread2的结果
3.2 unlock必须发生在lock之后
3.3 volatile修饰的变量,对一个变量的写操作先于对该变量的读操作
3.4 传递规则,操作A先于B,B先于C,那么A肯定先于C
3.5 线程启动规则,线程start方法肯定先于run
3.6 对象中断规则,interrupt这个动作,必须发生在捕获该动作之前
3.7 对象销毁规则,初始化必须发生在finalize之前
3.8 线程终结规则,所有的操作都发生在线程死亡之前