并发编程基础原理
并发编程基础原理,个人理解,如有错误请指正。推荐书籍《Java并发编程的艺术》
JMM (内存模型)
内存模型
每个线程都有一个私有的本地内存,本地内存中存储了该线程使用的共享变量的副本
本地内存只是JMM的一个抽象,具体实现依赖于具体硬件(高速缓存,寄存器等)
线程不直接读写主内存的共享变量,而是直接操作本地内存中的副本
硬件架构(常见架构,英特尔等)
结合JMM和硬件架构
当线程1抢占了核1的时间片,就拥有了核1及核1的L1 L2及共享L3使用权
在抢占的时间片内,线程1执行过程中若需要访问共享变量,就会去高速缓存中查找,若缓存miss则从主内存(也有可能从其他核的L1L2中查找)加载(此处包含预加载,为什么要有预加载见局部性原理)
通常所说的线程上下文切换过程中性能损耗是指第2步中缓存miss后的操作
若高速缓存中的副本被线程修改,cpu自行决定何时(一般是cpu空闲时)把修改后的值回写到主内存
若遇到Lock指令,cpu会强制回写主内存
步骤4,5中的回写主内存操作会使在其他缓存了该内存地址的数据(整个缓存行cacheLine)无效
当其他线程抢占任一核的时间片后,若高速缓存中的副本失效,会从主内存中重新加载共享变量
高速缓存和主内存一致性同步参考MESI一致性协议
volatile
顺序性
可见性
可见性定义
可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
JVM如何实现volatile的可见性
JVM解释执行或者编译执行时,若遇到共享变量的写操作会强制追加Lock指令
之后的过程见上面的步骤5,6,7
伪共享(并发编程的性能杀手)
伪共享描述
综合以上所述:缓存行容量为64字节(通常是,也有32的),缓存行中可以存放多个变量的副本(eg:8个long型),缓存行中任意一个变量被回写到主内存都会引起整个缓存行失效,无形中影响到了其他无竞争关系线程读取其他变量副本的成本。若这些变量中有一个被volatile修饰,那么该变量的写操作必定触发回写主内存,必定会使其他变量的副本失效,所以volatile要慎用
如何解决
padding ,eg:Disruptor
@sun.misc.Contended
(java8及以上)
关于synchronized锁优化
锁升级不可逆,目的是为了提高获得锁和释放锁的效率。锁升级过程发生在运行期。编译期Jvm也会做一些优化:锁擦除,锁粒度调整
偏向锁
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。 —— 摘自《并发编程的艺术》
优点:惰性解锁,竞争时才解锁,同一线程再次获取锁时无需再加锁
缺点:偏向锁撤销需要等待全局安全点,暂停线程
轻量级锁
自旋锁
自适应自旋锁
重量级锁
不自旋直接阻塞线程
AQS(J.U.C.基础)
AQS
条件队列
局部性原理(扩展)
时间局部性
空间局部性
分支局部性
等等
空间局部性
如果某个位置的信息被访问,那和它相邻的信息也很有可能被访问到。 这个很好理解,程序代码中有很多循环遍历数组等操作。不仅限于数组,线性(分布是连续的)数据结构都可以,比如Java对象的各个字段在堆中就是连续的内存块分布(详见Java对象的内存布局)
空间局部性的应用
主内存缓存硬盘 page cache :kafka,mysql等
mysql某个索引(索引在b+树叶子节点也是线性分布的)被命中后会一同加载该索引前后多条记录到同一个pageCache中,这样可以减少磁盘IO次数,提升查询性能。kafka的消息log在磁盘中也是按分区内的消息顺序追加的,也可以很好的利用该特性
cpu高速缓存主内存 cache line:redis等
redis中的数据结构散列集Hash,当key数量小于一定值时散列集会被压缩成数组就是为了利用cpu高速缓存,这也是为什么使用Hash比使用多个KV要快的原因。详见redis官网-内存优化
MESI缓存一致性协议(扩展)
M(修改,Modified)
本地处理器已经修改缓存行,即是脏行,它的内容与内存中的内容不一样,并且此 cache 只有本地一个拷贝(专有)
E(专有,Exclusive)
缓存行内容和内存中的一样,而且其它处理器都没有这行数据
S(共享,Shared)
缓存行内容和内存中的一样,有可能其它处理器也存在此缓存行的拷贝
I(无效,Invalid)
缓存行失效, 不能使用
Jvm堆中对象的内存布局
本文使用 mdnice 排版
版权声明: 本文为 InfoQ 作者【刚刚🏂】的原创文章。
原文链接:【http://xie.infoq.cn/article/d10d233aff196f245a5a7e795】。未经作者许可,禁止转载。
评论