Java并发机制的学习—synchronized(偏向锁和轻量级锁)

锁的实现原理

锁具体表现的三种形式

1、对于普通的同步方法,锁是当前的实例对象。

2、对于静态同步方法,锁是当前类的Class对象。

3、对于同步方法块,锁是Synchronized括号里配置的对象。

当一个线程试图访问同步代码块时,它首先要得到锁,退出或者抛出异常的时候必须释放锁。

JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令来实现的,方法同步虽然与代码块同步的在细节方面有所不同,但是也可以用monitorenter和monitorexit来解释:

monitorenter指令在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法的结束初和异常处,JVM要保证每个monitorenter都要有一个monitorexit与之相对应。任何对象都要有一个monitor与之相关联,当一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会获取对象所对应的monitor的所有权,即尝试获取对象的锁。

注意:Synchronized用的锁是存在Java的对象头里的。

偏向锁与轻量级锁

锁的级别:

无锁状态<偏向锁状态<轻量级锁状态<重量级锁状态

注意:锁只能升级,不能降级

1、偏向锁

       HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程的竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单的测试对象头的Mark Work里是否存储着指向当前线程的偏向锁。如果测试成功则表示已经获得了锁,如果测试失败,则需要再测试下Mark Word中偏向锁的标识是否设置成1(表示当前时偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

(1)偏向锁的撤销

          偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程试图竞争偏向锁的时候,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(这个时间点没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的进程是否还活着,如果线程不处于活动的状态,则将对象头设置成无锁的状态;如果线程仍然活着,则拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么回复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

(2)关闭偏向锁

          偏向锁在Java6和Java7中默认时启用的,但是它是在应用程序启动几秒之后才激活,如果有必要,可以通过JVM参数来关闭延迟。如果能确定应用程序的锁通常都处于一种竞争的状态,可以通过JVM来关闭偏向锁。

2、轻量级锁

(1)轻量级锁加锁

      线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

(2)轻量级锁解锁

       轻量级锁解锁时,会使用原子的CAS操作将Displaced Mark Word 替换回对象头,如果成功。则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成为重量级所。

       因为自旋会消耗CPU,为了避免无用的自旋(比如获得的锁被阻塞住了),一旦锁升级成重量锁,就不会再会恢复到轻量级的锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后就会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

发布了16 篇原创文章 · 获赞 17 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/TofuCai/article/details/87898018