可见性
问题
每个线程读取r会在内存中拷贝到自己线程使用的缓存中,当其中一个线程改变r值,另外一个线程是感知不到的。
解决办法
volatile保证可见性
CPU缓存体系
缓存行
cacheLine:因为程序局部性原理,按快读取,可以提高效率。一般64字节。
伪对齐:线程读取到自己不需要数据,而且要为不需要的数据同步花费时间,执行效率底下。
解决:@Contended缓存行对齐,避免伪对齐。
硬件保证缓存一致性
缓存一致性协议,保证线程之间缓存内容一致。
Intel: MESI一致性协议,cpu每个cache line标识4中状态(修改,共享,独占,无效)。
有序性
问题
大家都知道cpu执行没有前后依赖的指令是乱序执行的,当然java在执行字节码指令也是乱序的,只要保证单线程情况下输出结果是一致就可以。这样可以极大的提升执行效率。
但是也有一些问题:在多线程情况会出问题的。下面举一个对象半初始化的例子。
Object o=new Object();
字节码指令:
有肯能在执行new开辟内存空间后,就先去执行astore指令,告诉别的线程对象已经初始化完毕,但是执行初始化方法的指令还有调用,导致对象的数据错误。
原子性
问题
多线程条件下访问共享数据时候,产生竞争,导致数据不一致;需要线程同步保证数据一致。
n++(不是原操作): 1.读取数据 、2.加1 、3.赋值写会内存
解决
悲观锁
synchronized保证原子性: 本质(并发编程序列化)
monitor(管程):锁
临界区:锁的代码段,也就是锁的粒度。
synchronized可以保证可见性,加锁解锁会和内存进行同步。
乐观锁
CAS自旋锁
AtomicInteger使用CAS保证原子性。
Hostpot源码:
使用指令lock cmpcxg指令,但是这条指令也不是原子性的,使用lock锁实现的。
使用场景
synchronized:临界区比较大,等待人多。
CAS自旋锁:等待人少。