java并发相关(四)——关于synchronized的可重入性,线程切换实现原理与是否公平锁

 

一、可重入性

  关于synchronized的可重入性的证明,我们可以通过A类内写两个同步方法syncA(),syncB()。然后syncA内调用syncB,调用syncA发现代码可正常执行,来证明这一点。

 

  当处于无锁阶段时,划掉,都重入了不可能处于无锁。

 

  当处于偏向锁阶段时,由之前对偏向锁的解释可知,偏向当前线程id是,当前线程可直接运行。

 

  当处于轻量级锁阶段时,CAS失败后m虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。已知超过一个线程自旋,立即进入重量级锁,所以公平锁只需要在重量级锁中进行探讨。

 

  当处于重量级锁阶段时,Mark Word指向monitor对象的起始地址,而在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码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对象的线程,重入时比对为当前线程是,只需要_count自增即可。

二、线切换原理

  下面我们看一下重量级锁切换的实现图:

  上图中有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。

  • Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
  • Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中;
  • Wait Set:哪些调用wait方法被阻塞的线程被放置在这里;
  • OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck;
  • Owner:当前已经获取到所资源的线程被称为Owner;
  • !Owner:当前释放锁的线程。

  JVM每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选竞争线程。Owner线程会在unlock时,将ContentionList中的部分线程迁移到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)。Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM中,也把这种选择行为称之为“竞争切换”。这其实也就是我们常说的synchronized线程切换的实现原理

  OnDeck线程获取到锁资源后会变为Owner线程,而没有得到锁资源的仍然停留在EntryList中。如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,直到某个时刻通过notify或者notifyAll唤醒,会重新进去EntryList中。

  处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。

三、是否公平锁

  Synchronized是非公平锁。 Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。

参考资料:https://blog.csdn.net/kirito_j/article/details/79201213

posted @ 2019-10-17 00:10  豆豆323  阅读(720)  评论(0编辑  收藏  举报