synchronized的实现与应用

     在并发编程中,synchronized一直是一个元老级的角色,很多人称之为重量级锁。但是随着jdk1.6进行了各种优化之后,有些情况下它就并不那么重了。为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁。

     Java中每一个对象都可以成为锁,具体表现为以下3种。

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

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

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

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

JVM基于进入和退出Monitor对象来实现方法同步和代码同步,但两者的实现细节不一样。

       monitorenter指令在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有monitorexit与之配对。每一个对象都有一个monitor与之关联,并且一个monitor被持有后,它将处于被锁定状态。

 JAVA对象头

    synchronized用的锁是存在Java对象头里的。

    Mark Word:存储对象的hashCode或锁信息以及分代年纪。

    锁的升级与对比

    jdk1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁“。

    锁一共有四种状态,级别从低到高依次为:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。   

1.偏向锁

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

       偏向锁的撤销:

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

       关闭偏向锁:

       偏向锁在Java6和Java7里默认是启用的。

2.轻量级锁

       轻量级锁加锁

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

      轻量级锁解锁

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

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

     

       

    

猜你喜欢

转载自my.oschina.net/u/3126880/blog/1821379