并发操作中的3大问题:原子性问题,可见性问题,有序性问题
原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
有序性:程序执行的顺序按照代码的先后顺序执行
问题产生的原因
线程切换带来的原子性问题
案列:
假设为一个32位的变量赋值包括两个过程:为低16位赋值,为高16位赋值。可能发生一种情况:当将低16位数值写入之后,突然被中断,而此时又有一个线程去读取该变量的值,那么读取到的就是错误的数据。
缓存导致的可见性问题
案例:
//线程1执行的代码
int i = 0;
i = 10;
//线程2执行的代码
j = i;
假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中。此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10。这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。
编译优化带来的有序性问题
案列:
//线程1:
context = loadContext(); //语句1
inited = true; //语句2
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了指令重排序,在线程1执行过程中先执行语句2,而此是线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomethingwithconfig(context)方法,而此时context并没有被初始化,就会导致程序出错。
解决办法
JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
synchronized、volatile、LOCK,可以解决可见性问题(volatile不能解决原子性问题,可以解决有序性问题)
synchronized、LOCK、volatile、Happens-Before 规则可以解决有序性问题