并发底层详解
volatile的定义和实现原理
1 cpu术语定义
内存屏障: 是一组处理器指令,用于实现对内存操作的顺序限制
缓冲行: cpu高速缓存中可以分配的最小存储单位.处理器填写缓存行时会加载整个缓存行,现代cpu需要执行几百次cpu指令
2 volatile编译之后会产生Lock指令
1)将当前处理器缓存行的数据写回到系统内存
2)这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效
为了提高处理速速,处理器不直接和内存进行通信,而是先将内存的数据读到内部缓存后再进行操作,但操作完之后不知道何时会写回内存.如果对volatile的变量进行写操作,jvm就会像处理器发送一条Lock前缀指令,将这个变量的缓存行的数据写回系统内存.为了保证每个处理器的缓存是一致的,会使用处理器嗅探技术在总线上传播的数据检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
3 synchronized实现原理和应用
代码块monitorenter和monitorexit指令.
4 java对象头
synchronized用的锁存在java对象头里,如果对象是数组类型,则虚拟机用三个字宽(word)存储对象头,如果对象是非数组类型,则用两个字宽存储对象头.
java对象头里的mark word: hashcode, 分代年龄, 是否是偏向锁,锁标志位
5 锁的升级对比
无锁-->偏向锁-->轻量级锁-->重量级锁
1) 偏向锁
hotSpot作者经过研究发现,大多数情况下,锁不仅存在多线程竞争,而且总是由同一个线程获得,为了让同一个线程获得锁的代价更低,引入了偏向锁.当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程id,以后该线程就直接在测试一下mark word中偏向锁的标识是否设置成1:如果没有设置,则使用cas竞争锁;如果设置了,则尝试使用cas将对象头的偏向锁指向当前线程.
2) 轻量级锁 线程在执行同步块之前,jvm会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的mark word复制到锁记录中,官方称为displaced mark word.然后线程尝试使用cas将对象头中的mark word替换为指向锁记录的指针.如果成功,则当前线程获得锁,如果失败,则表示其他线程竞争锁,当前线程使用自旋锁来获取锁. 3) 重量级锁 轻量级解锁后,会使用原子的cas操作将displaced mark word 替换回到对象头,如果成功,则表示没有竞争发生.如果失败,表示当前锁存在竞争,锁就会膨胀称为重量级锁.
6 java实现原子操作
1)从java 1.5开始,jdk的并发包里提供了一些类来支持原子操作
2) cas实现原子操作的三大问题: ABA,循环时间开销大,只能保证一个共享变量的原子操作.
7 java内存模型的抽象结构
java线程之间的通信由java内存模型控制,jmm决定一个线程对共享变量的写入何时对另一个线程可见.从抽象的角度来看,jmm定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每一个线程都有一个本地内存存储了该线程读 写共享变量的副本.线程a和线程b要实现通信,必须线程a把本地内存更新过的共享变量刷新到主内存中去,线程b到主内存中去读取线程a之前已更新过的共享变量.
8 happens-before原则
1) 程序顺序规则: 一个线程的每个操作,happens-before于该线程中任意的后续操作
2) 监视器锁规则: 对一个对象的解锁,happens-before于随后对这个锁的加锁
3) volatile规则: 对一个volatile域的写,happens-before于任意后续对这个volatile的读
4) 传递性: 如果a happens-before b, b happens-before c,那么a happens-before c.