Volatile
volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性,如果一个字段被声明为volatile,java线程内存模型确保所有线程看到这个变量的值是一致的,即当一个线程修改一个共享变量时,另外线程能读到这个修改的值
实现原理
在对有volatile修饰的共享变量进行写操作时会多出一行Lock前缀的指令,该指令主要做两件事:
- 将当前处理器缓存行的数据写回到系统内存
- 使其他CPU里缓存了该内存地址的数据无效
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存数据读到内部缓存后再进行操作。如果对声明了volatile的变量进行写操作,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存,同时使其他缓存行里缓存了该内存地址的数据无效,需要重新从系统内存中将数据读到处理器缓存
synchronized
synchronized用的锁是存在Java对象头里的,如果对象是字节类型,则虚拟机用3个字宽存储对象头,如果对象是非数组类型,则用2个字存储对象头
对象头包括3部分:mark word(存储对象的hashcode或锁等信息)、指向类的指针、数组的长度(只有数组的对象用到);在运行期间,mark word里存储的数据会随着锁标志位的变化而变化,以32位JVM为例,mark word可能变化存储以下数据:
图片来源:https://ask.qcloudimg.com/http-save/yehe-3984104/6nlu6vcab7.png?imageView2/2/w/1620
在Java SE1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态
轻量级锁状态、重量级锁状态,这几种状态会随着竞争情况逐渐升级,锁可以升级但不能降级
无偏向锁:
图片来源:(https://blog.csdn.net/ds19980228/article/details/84189273)
轻量级锁:线程在执行同步块之前,JVM先会在当前线程的栈帧种创建用于存储锁记录的空间,并将对象头中的mark word复制到锁记录中,然后线程尝试使用CAS将对象头中的mark word替换为指向锁记录的指针,如果成功当前线程获得锁,如果失败表示其他线程竞争锁,当前线程便尝试使用自旋来获得锁;轻量级解锁时会使用原子CAS操作将mark word替换回到对象头,如果成功则表示没有竞争发生,如果失败表示当前锁存在竞争,锁就会膨胀成重量级锁
原子操作的实现原理
处理器实现原子性操作:
- 通过总线锁保证原子性:所谓总线锁就是使用处理器提供的一个Lock#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将会被阻塞,该处理器可以独占内存空间;在同一时刻我们只需保证对内存某个内存地址的操作是原子性,但总线锁定把CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销大
- 通过缓存锁来保证原子性:内存区域如果被缓存在处理器的缓存行中,它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效
Java实现原子性:Java中可以通过锁和CAS实现原子性
CAS实现原子性的三大问题:
- ABA问题:一个值A如果经历 A->B->A,那么使用CAS进行检查时会发现它的值没有发生改变,但实际却是变了;解决思路使用带版本号的CAS,JDK的atomic包提供了AtomicStampedReference 类解决ABA问题
- 循环开销大:如果自旋CAS长时间不成功,会给CPU带来非常大的执行开销
- 只能保证一个共享变量的原子性:把多个共享变量合并成一个共享变量来操作,JDK提供了atomicReference 类来保证引用对象之间的原子性,可以把多个变量放在一个对象里进行CAS操作
参考书籍:《Java并发编程的艺术》