深入学习重点分析java基础---第二章:java并发 synchronized

1.由浅入深首先要有 乐观锁、悲观锁的概念

  乐观锁:CAS(比较并替换)  乐观锁从乐观的角度看待并发问题,也就是乐观锁默认不存在并发问题,只是线程去修改数据的时候发现数据已经被修改了,才会返回修改失败的响应,乐观锁允许线程自旋尝试获取锁

  悲观锁:synchronized、reentranLock以及数据库for update等,悲观锁默认会有线程争抢资源.所以每次线程操作前会尝试抢占锁,如果锁已经被占用就阻塞当前线程,并等待持有锁的线程释放锁、

2.可重入锁

  synchroized和reentranLock都是可重入锁,可重入锁的意思是,当一个线程获取了该锁后,还能再次获取该锁,并且将重入数加1,每次线程释放锁 该计数减1

  为什么要设计为可重入锁?当线程已经持有该对象的monitor锁时再次访问受限资源时monitor计数器会加1,当访问结束jvm释放monitor锁时,monitor计数器会减1,计数器为0则完全释放该对象的monitor锁

3.synchronized的锁升级

  jdk1.6对synchronized进行了非常大的优化,使得synchronized与reentranLock性能非常接近,甚至在高并发情况下效率高与reentranLock

  整个升级过程为

  无锁 --------> 偏向锁 --------> 轻量级锁 --------> 重量级锁

  首先没有线程获取对象的monitor锁时,对象处于无锁状态

  

  当线程获取对象的monitor锁时,会在对象头和栈帧的锁记录里存储当前线程的ID,作为偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁只需测试Mark Word里线程ID是否为当前线程

  如果测试成功,表示线程已经获得了锁。如果测试失败,则需要判断偏向锁的标识。如果标识被设置为0(表示当前是无锁状态),则使用CAS竞争锁;如果标识设置成1(表示当前是偏向锁状态)

  则尝试使用CAS将对象头的偏向锁指向当前线程,触发偏向锁的撤销。偏向锁只有在竞争出现才会释放锁当其他线程尝试竞争偏向锁时,程序到达全局安全点后(没有正在执行的代码),它会查看Java

  对象头中记录的线程是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程可以竞争将其设置为偏向锁如果存活,那么立刻查找该线程的栈帧信息,如果还是需要继续持有这个锁对象

  那么暂停当前线程,撤销偏向锁,升级为轻量级锁,如果线程1不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程

  需要注意的是对象头中有一个epoch值,该值会记录对象偏向锁改变的次数,可以理解为偏向锁的版本号

  

  这个epoch有什么用呢?就是当虚拟机认为此类对象被撤销的次数超过一定次数 ( -XX:BiasedLockingBulkRebiasThreshold,默认为 20)之后认为该换代了

  也就是每次锁对象被撤销偏向的时候都会记录着,当超过阈值之后,对象对应的类里面的epoch就会升级,也就是+1。

  当类对象被撤销的次数超过一定次数 (-XX:BiasedLockingBulkRevokeThreshold,默认值为 40),JVM会认为此类对象都不适合偏向,会撤销所有此类实例的偏向锁,并且之后的加锁直接轻量级,没偏向了

 

  

  当对象处于轻量级锁状态时,线程在获取monitor时,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的MarkWord复制到锁记录中,即Displaced Mark Word然后线程会尝试使用CAS将对象头中的Mark Word

  替换为指向锁记录的指针。如果成功,当前线程获得锁。如果失败,表示其他线程在竞争锁,当前线程使用自旋来获取锁。当自旋次数达到一定次数时,锁就会升级为重量级锁。

  轻量级锁解锁时,会使用CAS操作将Displaced Mark Word替换回到对象头,如果成功,表示没有竞争发生。如果失败,表示当前锁存在竞争,锁已经被升级为重量级锁,则会释放锁并唤醒等待的线程。

4.各种锁状态下对象头中的数据

  

  synchronized的锁状态存在Java对象头里,Java对象头里的Mark Word默认存储对象的HashCode、分代年龄和锁标记位。在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。32位JVM的Mark Word可能变化存储为以下5种数据:

面试必问---synchronized实现原理及锁升级过程你懂吗?

                    图1

  可以看到当对象处于无锁状态之外的任何状态时,都没有存储HashCode,那么此时如何获取对象的HashCode呢

  首先这里的HashCode指的是jvm中的 identity hash code 与类中自定义的hashCode()方法不是一回事,如果没有自定义hashCode()方法,也就是调用的Object对象的hashCode方法时返回的才是identity hash code

  这里需要注意几点

  • 当一个对象已经计算过identity hash code,它就无法进入偏向锁状态;
  • 当一个对象当前正处于偏向锁状态,并且需要计算其identity hash code的话,则它的偏向锁会被撤销,并且锁会膨胀为重量锁;
  • 重量锁的实现中,ObjectMonitor类里有字段可以记录非加锁状态下的mark word,其中可以存储identity hash code的值。或者简单说就是重量锁可以存下identity hash code。

 5.synchroized底层实现原理

  synchroized在未发生激烈竞争的情况下不会进入到重量级锁状态,此前都是使用自旋的方式阻塞线程,而大家都知道自旋锁只在用户态就能做到,只是会消耗cpu资源

  而synchroized进入到重量级锁之后则是切换到内核态阻塞线程,从图1中可以看到重量级锁只存储了指向互斥量(重量级锁)的指针

  这里的指针指向的其实就是上面提到过多次的monitor锁的起始地址

  每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态

  在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(ObjectMonitor.hpp文件,C++实现的)

  

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;  //_owner指向持有ObjectMonitor对象的线程
    _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

  ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象);
  整个monitor运行的机制过程如下:
  _owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1

  若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。
  具体见下图:
在这里插入图片描述
  因此,monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因

  同时也是notify/notifyAll/wait等方法存在于顶级对象Object中的原因

ps:文中有借鉴以下博客中的内容,感谢几位博主分享了这么多优质内容

https://blog.csdn.net/mulinsen77/article/details/88635558

https://www.zhihu.com/question/52116998?sort=created

posted @ 2020-07-12 23:27  waen  阅读(147)  评论(0编辑  收藏  举报