volatile
volatile的定义
如果一个变量被volatile修饰,那么Java线程内存模式会保证所有线程看到这个变量的值都是一致的。
volatile的实现原理
被volatile修饰的共享变量在进行写操作的时候,编译会多出一行带有Lock前缀的汇编代码。 带有LOCK前缀的指令在多核处理器下会做两件事情:
- 会引起处理器缓存回写到内存
- 一个处理器缓存会写到内存会导致其他处理器的缓存无效
synchronized
synchronized锁定的对象
- 普通同步方法:锁定的对象是当前实例
- 同步方法块:锁定的对象是synchronized括号内配置的对象实例
- 静态同步方法:锁定的对象是当前类的Class对象实例
synchronized的实现原理
JVM是通过进入和退出monitor对象来实现方法同步和代码块同步。代码块同步是通过monitorenter和monitorexit指令来实现的,而方法同步是通过另一种方式来实现的。但是方法同步也可以通过monitorenter和monitorexit指令来实现。
monitorenter指令是在编译后插入到同步代码块的开始位置,monitorexit则插入到方法结束处和异常处。JVM保证每个monitorenter指令必须有一个monitorexit指令与之匹配。
每个对象都会有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。当线程执行到monitorenter指令的时候,会尝试获取对象所对应的monitor的所有权,即尝试获取对象的锁。
原子操作
处理器如何实现原子操作?
处理器支持总线锁定和缓存锁定两种方式来保证复杂内存操作的原子性。
使用总线锁支持原子操作
处理器提供了LOCK#信号。当一个处理器在总线上声言LOCK#信号的时候,其他处理器的请求会被阻塞,那么该处理器就可以独占共享内存。
使用缓存锁定支持原子操作
使用总线锁定,会导致其他处理器无法操作其他内存地址的数据,所以总线锁定的开销大。
频繁使用的内存会被缓存到处理器的高速缓存中,那么原子操作就可以直接在处理器内部缓存进行,可以避免在总线上声言LOCK#信号。
缓存锁定就是某个处理器对缓存的数据进行变更的时候,会通知缓存了该数据的其他处理器抛弃缓存的数据或重新从内存中读取数据。
在某些情况下,处理器不会使用缓存锁定
- 当操作的数据不能被缓存,或操作的数据跨多个缓存行的时候,只能使用总线锁定。
- 处理器不支持缓存锁定。
Java如何实现原子操作?
Java使用CAS操作或者锁来实现原子操作。
使用循环CAS来实现原子操作
使用CAS有三大问题:ABA问题、循环时间长开销大、只支持一个共享变量的原子操作
- ABA问题。CAS操作在更新变量前进行比较,比较成功则更新。如果一个变量的值是A,然后变成B,又变成A,那么CAS在比较的时候就会认为这个变量没有发生改变,其实已经发生了变化。ABA问题的解决思路就是在变量前面加一个版本号。
- 循环时间长开销大。如果自旋CAS长时间不成功,会给CPU带来非常大的执行开销。
- CAS只支持一个共享变量的原子操作。
使用锁机制来实现原子操作
只有获得锁的线程才能对锁定的内存区域进行操作。