zoukankan      html  css  js  c++  java
  • 【Java】唠唠synchronized中的重量级锁

    • 说到轻量级锁,我们必须先说一下轻量级锁是什么?

    synchronized在JDK1.6之后的优化锁后,一共有四种锁阶段:

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

    而重量级锁,正处于是第四种阶段,即当某个线程长时间占有锁资源,而其他线程一直处于自旋状态并竞争激烈,锁将会升级为重量级锁。

    • 子曾经曰过,“Don't BB,Look at the picture”

    那么这个组件都是个啥呢?

    ContentionList:所有请求锁的线程将被首先放在该竞争队列。
    
    EntryList:ContentList有资格成为候选人的线程被移到EntryList中。
    
    WaitSet存放那些调用wait()方法被阻塞的线程
    
    OnDeck任何时刻最多只能有一个线程竞争锁,该线程成为OnDeck
    
    Owner获得锁的线程

    当一个线程尝试获取锁时,如果该锁已经被占用,那么会将该线程封装成一个【ObjectWaiter】对象,

    插入到【ContentionList】中,然后调用【park】函数,将当前线程阻塞挂起,释放CPU资源

    (在Linux上,pack函数式直接调用底层gdlib库的【pthread_cond_wait】,ReentrantLock底层调用的【Unsafe.park()】也是调用的这个底层)

    当线程释放锁时,会从【ContentionList】或【EntryList】中随机唤醒一个线程,被唤醒的线程则放入【Ready Thread】中,

    【onDeck】可以理解为是“假定继承人”,由于synchronized是非公平的,所以假定继承人也不一定能拿到锁。

    若某个线程获取到锁后调用【Object#wait】方法,则会将线程加入到【WaitSet中】中,线程处于阻塞状态,

    当被【Object#notify】唤醒后,会将线程从【WaitSet】中移动到【ContentionList】或【EntryList】中。

    此处要注意一点:若一个锁对象调用wait或notify,若当前锁级别是轻量级锁或偏向锁,那么将膨胀为重量级锁。

    • 我们从一段源码来分析一下整体过程:
    void ATTR ObjectMonitor::EnterI (TRAPS) {
        Thread * Self = THREAD ;
        ...
        // 尝试获得锁
        if (TryLock (Self) > 0) {
            ...
            return ;
        }
    
        DeferredInitialize () ;
     
        // 自旋
        if (TrySpin (Self) > 0) {
            ...
            return ;
        }
        
        ...
        
        // 将线程封装成node节点中
        ObjectWaiter node(Self) ;
        Self->_ParkEvent->reset() ;
        node._prev   = (ObjectWaiter *) 0xBAD ;
        node.TState  = ObjectWaiter::TS_CXQ ;
    
        // 将node节点插入到_cxq队列的头部,cxq是一个单向链表
        ObjectWaiter * nxt ;
        for (;;) {
            node._next = nxt = _cxq ;
            if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
    
            // CAS失败的话 再尝试获得锁,这样可以降低插入到_cxq队列的频率
            if (TryLock (Self) > 0) {
                ...
                return ;
            }
        }
    
        // SyncFlags默认为0,如果没有其他等待的线程,则将_Responsible设置为自己
        if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
            Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
        }
    
    
        TEVENT (Inflated enter - Contention) ;
        int nWakeups = 0 ;
        int RecheckInterval = 1 ;
    
        for (;;) {
    
            if (TryLock (Self) > 0) break ;
            assert (_owner != Self, "invariant") ;
    
            ...
    
            // park self
            if (_Responsible == Self || (SyncFlags & 1)) {
                // 当前线程是_Responsible时,调用的是带时间参数的park
                TEVENT (Inflated enter - park TIMED) ;
                Self->_ParkEvent->park ((jlong) RecheckInterval) ;
                // Increase the RecheckInterval, but clamp the value.
                RecheckInterval *= 8 ;
                if (RecheckInterval > 1000) RecheckInterval = 1000 ;
            } else {
                //否则直接调用park挂起当前线程
                TEVENT (Inflated enter - park UNTIMED) ;
                Self->_ParkEvent->park() ;
            }
    
            if (TryLock(Self) > 0) break ;
    
            ...
            
            if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
    
               ...
            // 在释放锁时,_succ会被设置为EntryList或_cxq中的一个线程
            if (_succ == Self) _succ = NULL ;
    
            // Invariant: after clearing _succ a thread *must* retry _owner before parking.
            OrderAccess::fence() ;
        }
    
       // 走到这里说明已经获得锁了
    
        assert (_owner == Self      , "invariant") ;
        assert (object() != NULL    , "invariant") ;
      
        // 将当前线程的node从cxq或EntryList中移除
        UnlinkAfterAcquire (Self, &node) ;
        if (_succ == Self) _succ = NULL ;
        if (_Responsible == Self) {
            _Responsible = NULL ;
            OrderAccess::fence();
        }
        ...
        return ;
    }
    • 那么重量级锁是如何实现重入的呢?

    在Monitor中其实还有一个计数器,主要是用来记录重入次数的,当计数器为0时,表示没有任何线程持有锁,

    当某线程获取锁时,计算器则加1,若当前线程再次获取锁时,计数器则会再次递增,

    不过sychronized属于隐式锁,因为不需要手动解锁;而ReentrantLock属于显式锁,需要通过unlock开解锁。

    下面代码就是synchronized的锁重入源码:

      // 如果是重入的情况
      if (cur == Self) {
         // TODO-FIXME: check for integer overflow!  BUGID 6557169.
         _recursions ++ ;
         return ;
      }
      // 当前线程是之前持有轻量级锁的线程。由轻量级锁膨胀且第一次调用enter方法,那cur是指向Lock Record的指针
      if (Self->is_lock_owned ((address)cur)) {
        assert (_recursions == 0, "internal state error");
        // 重入计数重置为1
        _recursions = 1 ;
        // 设置owner字段为当前线程(之前owner是指向Lock Record的指针)
        _owner = Self ;
        OwnerIsThread = 1 ;
        return ;
      }
    • 上文提到了很多次ReentrantLock,所以最后我再总结一下ReentrantLock和Synchronized的两者区别:
    1》synchronized是JVM层面的锁;ReentrantLock是JDK层面的锁,由java代码实现
    
    2》synchronized锁无法在代码中判断是否有所;ReentrantLock则可以通过【isLock()】判断是否获取到锁
    
    3》synchronized是一种非公平锁;ReentrantLock既可以实现公平锁,也可以实现非公平锁
    
    4》synchronized不可以被中断;ReentrantLock可以【lockInterruptibly】实现中断
    
    5》发生异常时,synchronized会自动释放锁,有javac实现;ReentrantLock需要开发者在finally中显式释放锁
    
    6》ReentrantLock在加锁时会更灵活,可以使用【tryLock】尝试获取锁,从而避免线程阻塞
    • 总结

    synchronized底层Monitor其实和ReentrantLock实现上还是有很多相似,比如数据结构,挂起线程方式等。

    所以两个锁之间还是有很多通性的,通常我们用synchronized可以解决很多问题了。

  • 相关阅读:
    开启LOH压缩?
    搭建Hadoop2.6.4伪分布式
    EntityFramework CodeFirst SQLServer转Oracle踩坑笔记
    glob模式
    在Oracle中使用Entity Framework 6 CodeFirst
    IE9,10中console对象的bug
    ViewBag是如何实现的
    esbuild vs webpack
    企业微信公众号本地调试auto2.0
    vmware15.5的解锁mac系统插件
  • 原文地址:https://www.cnblogs.com/boluopabo/p/13086172.html
Copyright © 2011-2022 走看看