Java并发编程艺术---java并发编程机制的底层实现原理

2.1volatile的应用
volatile是轻量级的synchronized,它在多处理器开发中保证了变量的“可见性”。由于使用volatile不会引起线程的上下文切换,所以如果使用得当,会比synchronized的使用和执行成本更低。
2.1.1volatile的定义与实现原理
对volatile修饰的变量进行反编译的时候,可以看到会有一个lock前缀的指令,这个指令在多核处理器下回做如下两件事情:
(1)将当前处理器缓存行的数据写回到系统内存。
(2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
2.2synchronized实现原理与应用
synchronized的具体表现形式:
(1)对于普通方法,锁是当前实例对象。
(2)对于静态方法,锁是当前类的Class对象。
(3)对于同步块,锁是synchronized括号里配置的对象。
JVM在进入和退出Monitor对象来实现方法同步和代码块同步,但是两者的实现方式是不一样的。
代码块同步的使用monitorenter和monitorexit指令实现的,方法同步使用的是ACC_SYNCHRONIZED标志来实现的。
任何一个对象都有一个montor与之关联,当一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,会尝试获取monitor对象的所有权。
2.2.1Java对象头
synchronized使用的锁是存在java对象头里的。java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。
2.2.2锁的升级和对比
锁的4中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。
锁的状态只能升级不能降级。
(1)偏向锁
在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获取锁的代价更低而引入了偏向锁。
当一个线程访问同步块并获取锁的时候,会在对象头和栈帧中的锁记录记录里存储偏向锁的线程ID,以后这个线程再次进入和退出同步块的时候不需要进行CAS操作来进行加锁和解锁,只需要简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如测试成功,表示线程已经获取到了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成了1(表示当前是偏向锁);如果没有设置为1,就是要CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
1、偏向锁的撤销
当其他线程竞争偏向锁时,持有偏向锁的线程才会释放锁。
偏向锁的撤销需要等到全局安全点,流程是:(1)暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如线程不活,则将对象头设置成无锁状态;如线程活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁活或者标记对象不合适作为偏向锁,最后唤醒暂停的线程。
(2)轻量级锁
1、轻量级锁加锁
线程在进入同步代码块之前,JVM会先在当前线程的栈帧找中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如成功,当前线程获取到锁,如失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
2、轻量级锁解锁
使用CAS操作将Mark Word替换到对象头,成功,则表示没有竞争发生,失败,表示当前锁存在竞争,锁就会升级成重量级锁。
为什么锁升级之后,就不能再降级了?
因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。
2.3原子操作的实现原理
1、处理器如何实现原子操作
基于总线加锁或者缓存加锁的方式来实现多处理器之间的原子操作。
2、两种情况下,处理器不会使用缓存锁定
(1) 当操作的数据不能被缓存在处理器内部,或者操作的数据跨多个缓存行的时候,则处理器会调用总线锁定。
(2)有些处理器不支持缓存锁定。
以上的两种情况,可以使用Intel处理器提供的Lock前缀指令来实现。
3、Java中如何实习原子操作
(1)使用循环CAS实现原子操作
JVM中的CAS操作正式利用了处理器提供的CMPXCHG指令实现。
从Java1.5开始,jdk的并发包里面提供了一些类来支持原子操作,如AtomicBoolean等
(2)CAS操作存在的问题
(1)ABA问题。
(2)循环时间长,开销大。
(3)只能保证一个共享变量的原子操作。
(3)使用锁机制实现原子操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章