本文为稀土技能社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

引言

咱们在Andoird性能优化 – 死锁监控与其背面的小知识这篇中提到过死锁检测的办法,其间咱们涉及到两个native层中供给给咱们重要办法,分别是检查当时线程持有的“锁”的GetContendedMonitor办法

ObjPtr<mirror::Object> Monitor::GetContendedMonitor(Thread* thread) {
    // This is used to implement JDWP's ThreadReference.CurrentContendedMonitor, and has a bizarre
    // definition of contended that includes a monitor a thread is trying to enter...
    ObjPtr<mirror::Object> result = thread->GetMonitorEnterObject();
    if (result == nullptr) {
        // ...but also a monitor that the thread is waiting on.
        MutexLock mu(Thread::Current(), *thread->GetWaitMutex());
        Monitor* monitor = thread->GetWaitMonitor();
        if (monitor != nullptr) {
            result = monitor->GetObject();
        }
    }
    return result;
}

还有便是获取当时“锁”地点的Thread的GetLockOwnerThreadId办法

uint32_t Monitor::GetLockOwnerThreadId(ObjPtr<mirror::Object> obj) {
    DCHECK(obj != nullptr);
    LockWord lock_word = obj->GetLockWord(true);
    switch (lock_word.GetState()) {
        case LockWord::kHashCode:
            // Fall-through.
        case LockWord::kUnlocked:
            return ThreadList::kInvalidThreadId;
        case LockWord::kThinLocked:
            return lock_word.ThinLockOwner();
        case LockWord::kFatLocked: {
            Monitor* mon = lock_word.FatLockMonitor();
            return mon->GetOwnerThreadId();
        }
        default: {
            LOG(FATAL) << "Unreachable";
            UNREACHABLE();
        }
    }
}

咱们所说的“锁”,都是以java层的视角去解释的,一起咱们模仿死锁的操作是用synchronized这个java层的关键字去模仿的。那么必定有读者有疑问,为什么native中虚拟机会供给出来这么几个办法?一起咱们可以留意到,在上面两个办法中,涉及到了Monitor,LockWord,mirror::Object等几个类,它们之间存在什么联系呢?为什么获取“锁”跟经过“锁”获取Thread id的操作都涉及到了它们呢?

本篇将从native层的视点,去探究java层的“锁”的真实完成

线程同步

咱们在java层常用的线程同步手段,synchronized可谓是一个非常抢手的用法,由于它是java/kotlin的关键字,天生就带有线程同步的能力。咱们举个比方,便是用synchronized去润饰代码块的场景

synchronized(xx){
同步逻辑
}

其实这段代码,便是在进入代码段的时分,生成了一条MONITOR_ENTER指令,而脱离代码段的时分,生成一条MONITOR_EXIT指令,当然,咱们并不那么关怀指令的真实在不同架构的完成,咱们关注能生成指令的native虚拟机上层代码,所以接下来咱们就来介绍一下Monitor,LockWord,mirror::Object这几个新成员了,它们之间的调用联系如图

ART虚拟机线程同步实现
在上面三个类中,真实负责供给同步机制的,其实“只要”Monitor这个类,它是Art虚拟机中的包装类

Monitor

依据上图,咱们可以看到Monitor中含有一个叫monitor_lock的成员变量,类型是Mutex,咱们也知道,虚拟机自身它是没有线程同步的功能的,因而真实同步的必定仍是要依靠操作系统自身的完成,操作系统供给的不同机制有许多,比方linux中的futex,比方pthread_mutex_t等,挑选哪种方式是依据当时系统架构决定的,比方可以经过ART_USE_FUTEXES宏判别同步机制是否由futex供给。

art/runtime/base/mutex.h
#if defined(__linux__)
#define ART_USE_FUTEXES 1
#else
#define ART_USE_FUTEXES 0
#endif

这儿咱们点到为止就好,真实的同步机制依据操作系统不同完成细节必定是不同的,咱们这儿仍是以认识Monitor类为主。

monitor_id是一个MonitorId的类型,其实便是说每一个Monitor目标都有个id,作为Monitor类的仅有标识,类型是32位的无符号整型

obj_,每一个Monitor都会相关一个Object目标

当然,Monitor还有许多个目标,这儿就不再一一举例,可以在art/runtime/monitor.h中看到Monitor的定义。Monitor自身还供给了Lock,UnLock等线程同步办法去给咱们程序使用。

Object

这儿说的Object可不是咱们java层的Object.java类,而是指native层的Object类(接下来咱们说的Object都是native层的Object),它有一个名为monitor_ 的成员变量,可是!!留意这儿,虽然Monitor类的obj_指向了一个Object目标,可是这儿的Object类的monitor_却并不是Monitor ,而是指向了一个LockWord目标。(为什么说是指向,由于存的类型是一个指针)

LockWord

LockWord,它只要一个uint32类型的成员变量,可以说这是一个非常“简练”的类,从上面咱们了解到,Onject类中的monitor_指向的是一个LockWord目标,那么为什么要指向这么一个目标呢?咱们来回忆一下,synchronized是不是可以润饰一个目标或许类自身,去完成一个同步操作。那么为什么无论是类仍是目标都能用synchronized润饰呢?其实便是由于java层的类或许目标都可以是native的Object的一个表现,而咱们Object里面都有monitor_目标。可是这也咱们考虑一下,并不是所有的java层目标或许类都能被synchronized润饰对不对,咱们再回忆一下Monitor,咱们说了Monitor背面其实仍是靠着操作系统的同步机制去现实线程同步,而这个操作系统同步操作,原本便是一个非常重量级的资源 因而关于Object来说,它没有直接与Monitor相关起来,而是以LockWord作为纽带,在需求的时分咱们才进行Monitor相关,然后达到资源最大化的作用。所以LockWord,自身可以算是一个中间层,也可以说是一个分发器,由它进行是否上锁操作。

LockWord源码在这,咱们可以看到,他定义了几种状况

enum LockState {
    kUnlocked,    // No lock owners. 未上锁
    kThinLocked,  // Single uncontended owner. ThinLock,瘦锁
    kFatLocked,   // See associated monitor. FatLock 胖锁
    这两个跟锁没有太大联系,咱们忽略
    kHashCode,    // Lock word contains an identity hash.
    kForwardingAddress,  // Lock word contains the forwarding address of an object.
};

咱们看到LockState的定义,其实就可以理解了,LockWord经过LockState,指明了当时Object处于一个怎样的同步状况。比方假如当时是kUnlocked状况,那么就不用去浪费Monitor的资源,假如是kFatLocked状况,那么咱们就需求去“绑定”Monitor了。那么LockWork怎样判别当时属于哪种情况呢?回忆一下上面的类图,其实便是经过成员变量value(32位)标识完成的

ART虚拟机线程同步实现

  • value的30-31位用来描述锁的形状,咱们只关怀锁的部分,00 代表KStateThinOrunlocked(对应着锁的无锁跟瘦锁状况),01 为kStateFat (胖锁)

  • KStateThinOrunlocked形状时,28-29代表一个ReadBarrier(读屏障),16-27代表一个计数器(记载该锁被调用的次数,比方线程递归调用),0-15位时持有锁线程的id

  • kStateFat形状时,28-29代表一个ReadBarrier(读屏障),0-27位代表一个Monitor目标的monitor_id

咱们猜猜看,为什么虚拟机要设计这么一个一个结构,咱们从咱们了解的synchronized去下手

synchronized native完成

咱们面试的时分必定背过synchronized,比方锁晋级流程呀,倾向锁转变为轻量级然后重量级这么一个进程,或许大部分咱们都只停留在了面经的阶段,下面咱们来看一下,为什么会有这么几个阶段,一起synchronized是怎样完成的。

回到开头,咱们说过synchronized会在代码块开始前插入MONITOR_ENTER指令,在结束时插入MONITOR_EXIT指令,这儿咱们并不关怀真实的操作系统指令,而是上一层的虚拟机代码生成,这两条分别对应着MonitorEnter 办法和MonitorExit

art/runtime/mirror/object-inl.h
inline ObjPtr<mirror::Object> Object::MonitorEnter(Thread* self) {
        return Monitor::MonitorEnter(self, this, /*trylock=*/false);
        }
        inline bool Object::MonitorExit(Thread* self) {
        return Monitor::MonitorExit(self, this);
        }

看到这儿,接下来便是硬核的扒源码环节

MonitorEnter


ObjPtr<mirror::Object> Monitor::MonitorEnter(Thread* self,
        ...
        while (true) {
        把接下来保存的内容放在LockWord中
        LockWord lock_word = h_obj->GetLockWord(false);
        switch (lock_word.GetState()) {
        假如是未上锁状况,则生成一个状况时ThinLock的LockWord,当时次数是0
        case LockWord::kUnlocked: {
        // No ordering required for preceding lockword read, since we retest.
        LockWord thin_locked(LockWord::FromThinLockId(thread_id, 0, lock_word.GCState()));
        if (h_obj->CasLockWord(lock_word, thin_locked, CASMode::kWeak, std::memory_order_acquire)) {
        AtraceMonitorLock(self, h_obj.Get(), /* is_wait= */ false);
        return h_obj.Get();  // Success!
        }
        continue;  // Go again.
        }
        假如当时是瘦锁,先判别当时具有锁的线程跟当时调用线程是否是同一个,假如是同一个线程,则添加ThinLockCount
        case LockWord::kThinLocked: {
        uint32_t owner_thread_id = lock_word.ThinLockOwner();
        if (owner_thread_id == thread_id) {
        // No ordering required for initial lockword read.
        // We own the lock, increase the recursion count.
        uint32_t new_count = lock_word.ThinLockCount() + 1;
        这儿有个细节,虽然是同一个线程假如超出kThinLockMaxCount,则经过InflateThinLocked变成胖锁
        if (LIKELY(new_count <= LockWord::kThinLockMaxCount)) {
        LockWord thin_locked(LockWord::FromThinLockId(thread_id,
        new_count,
        lock_word.GCState()));
        // Only this thread pays attention to the count. Thus there is no need for stronger
        // than relaxed memory ordering.
        if (!gUseReadBarrier) {
        h_obj->SetLockWord(thin_locked, /* as_volatile= */ false);
        AtraceMonitorLock(self, h_obj.Get(), /* is_wait= */ false);
        return h_obj.Get();  // Success!
        } else {
        // Use CAS to preserve the read barrier state.
        if (h_obj->CasLockWord(lock_word,
        thin_locked,
        CASMode::kWeak,
        std::memory_order_relaxed)) {
        AtraceMonitorLock(self, h_obj.Get(), /* is_wait= */ false);
        return h_obj.Get();  // Success!
        }
        }
        continue;  // Go again.
        } else {
        // We'd overflow the recursion count, so inflate the monitor.
        InflateThinLocked(self, h_obj, lock_word, 0);
        }
        } else {
        假如调用线程跟当时锁线程不是同一个,则添加contention_count
        if (trylock) {
        return nullptr;
        }
        // Contention.
        contention_count++;
        Runtime* runtime = Runtime::Current();
         假如contention_count 
        if (contention_count  <= kExtraSpinIters + runtime->GetMaxSpinsBeforeThinLockInflation() 时且contention_count > kExtraSpinIters,则主动让出当时cpu调度(这儿咱们可以考虑一下为什么)
        <= kExtraSpinIters + runtime->GetMaxSpinsBeforeThinLockInflation()) {
        if (contention_count > kExtraSpinIters) {
        sched_yield();
        }
        } else {
        contention_count = 0;
        // No ordering required for initial lockword read. Install rereads it anyway.
        InflateThinLocked(self, h_obj, lock_word, 0);
        }
        }
        continue;  // Start from the beginning.
        },
        假如是胖锁经过Monitor,目标mon->Lock(self);未持有锁时,会一直堵塞,lock之后便是成功获取到锁的逻辑
        case LockWord::kFatLocked: {
        std::atomic_thread_fence(std::memory_order_acquire);
        Monitor* mon = lock_word.FatLockMonitor();
        if (trylock) {
        return mon->TryLock(self) ? h_obj.Get() : nullptr;
        } else {
        mon->Lock(self);
        DCHECK(mon->monitor_lock_.IsExclusiveHeld(self));
        return h_obj.Get();  // Success!
        }
        ...

经过上面对MonitorEnter的剖析,咱们可以理解了它的中心,便是经过当时LockWord的状况去进行LockWord对应的晋级。为了描述简略,咱们接下来把LockWord称为“锁”,可是希望读者可以理解跟Monitor的差异,咱们这儿简略总结一下流程,首要分为了两个大分支,一个是当时调用线程跟锁的具有线程是同一个时与不一起的区分。

假如当时请求锁的线程跟锁的具有线程是同一个,那么就看当时LockWord的状况,假如是无锁状况,则生成一个状况处于瘦锁的LockWord,咱们再来回忆一下LockWord对应状况的不同状况

ART虚拟机线程同步实现
可以看到ThinState瘦锁状况有个lockcount的概念,便是锁的调用次数。从无锁变成瘦锁时,瘦锁初始化时lockcount便是0。假如当时是瘦锁,假如lockcount超过了LockWord::kThinLockMaxCount,4096,那么就要经过InflateThinLocked变成胖锁。假如是胖锁,就经过Lock办法直接获取(trylock为false),Lock办法会堵塞当时线程直到获取到锁

假如当时请求锁的线程跟锁的具有线程是不同的,未上锁跟胖锁的状况处理基本一致,关于当时处于瘦锁的时分,有个小优化点,便是if (contention_count <= kExtraSpinIters + runtime->GetMaxSpinsBeforeThinLockInflation() 时且contention_count > kExtraSpinIters,则主动让出当时cpu调度,这是由于线程让出cpu到线程从头获取cpu的时分,有一定的时间差,或许持有锁的线程现已开释了锁,这个时分就不需求堵塞了,直接获取功率更高。这算是art虚拟机的一个优化点,可是这个优化点还在一直迭代中,至少androd10 – android13现已更新好多个版本了,由于这个优化点有个弱点便是,多核cpu情况下,或许当时cpu让出调用,在其他的核中又立马获取到调度了,因而存在无用功的或许性比较大(无效调度),因而android13 时多了kExtraSpinIters这个纬度去判别是否让出线程调度的方案。

瘦锁晋级为胖锁

咱们在MonitorEnter进程中,可以看到在瘦锁处理进程中,会经过InflateThinLocked办法变成胖锁,咱们来了解一下这个晋级进程,InflateThinLocked内部会调用Inflate办法进行真实的晋级,inflate函数便是要将输入参数obj包括的LockWord目标跟Monitor绑定起来

void Monitor::Inflate(Thread* self, Thread* owner, ObjPtr<mirror::Object> obj, int32_t hash_code) {
        DCHECK(self != nullptr);
        DCHECK(obj != nullptr);
        // Allocate and acquire a new monitor.
        Monitor* m = MonitorPool::CreateMonitor(self, owner, obj, hash_code);
        DCHECK(m != nullptr);
        if (m->Install(self)) {
        if (owner != nullptr) {
        VLOG(monitor) << "monitor: thread" << owner->GetThreadId()
        << " created monitor " << m << " for object " << obj;
        } else {
        VLOG(monitor) << "monitor: Inflate with hashcode " << hash_code
        << " created monitor " << m << " for object " << obj;
        }
        Runtime::Current()->GetMonitorList()->Add(m);
        CHECK_EQ(obj->GetLockWord(true).GetState(), LockWord::kFatLocked);
        } else {
        MonitorPool::ReleaseMonitor(self, m);
        }
        }

ART虚拟机线程同步实现

inflate办法会调用Install办法,在这儿咱们关注一下瘦锁的晋级
bool Monitor::Install(Thread* self) NO_THREAD_SAFETY_ANALYSIS {
        Thread* owner = owner_.load(std::memory_order_relaxed);
        CHECK(owner == nullptr || owner == self || owner->IsSuspended());
        // Propagate the lock state.
        LockWord lw(GetObject()->GetLockWord(false));
        switch (lw.GetState()) {
        case LockWord::kThinLocked: {
        DCHECK(owner != nullptr);
        CHECK_EQ(owner->GetThreadId(), lw.ThinLockOwner());
        DCHECK_EQ(monitor_lock_.GetExclusiveOwnerTid(), 0) << " my tid = " << SafeGetTid(self);
        保存瘦锁的信息
        lock_count_ = lw.ThinLockCount();
        monitor_lock_.ExclusiveLockUncontendedFor(owner);
        DCHECK_EQ(monitor_lock_.GetExclusiveOwnerTid(), owner->GetTid())
        << " my tid = " << SafeGetTid(self);
        结构了一个胖锁目标,经过CasLockWord中cas操作把这个胖锁目标替换原来的瘦锁
        LockWord fat(this, lw.GCState());
        // Publish the updated lock word, which may race with other threads.
        bool success = GetObject()->CasLockWord(lw, fat, CASMode::kWeak, std::memory_order_release);
        if (success) {
        if (ATraceEnabled()) {
        SetLockingMethod(owner);
        }
        return true;
        } else {
        monitor_lock_.ExclusiveUnlockUncontended();
        return false;
        }
        }

inflate进程仍是比较直观的,经过inflate办法把瘦锁变成了胖锁的进程,其实是构建了一个新的LockWord目标,并设置状况为胖锁,后续经过cas操作把这个Object的LockWord目标进行替换

MonitorExit

bool Monitor::MonitorExit(Thread* self, ObjPtr<mirror::Object> obj) {
       ...
        while (true) {
        LockWord lock_word = obj->GetLockWord(true);
        switch (lock_word.GetState()) {
        // 反常case处理,没有上锁或许锁状况不对
        case LockWord::kHashCode:
        // Fall-through.
        case LockWord::kUnlocked:
        FailedUnlock(h_obj.Get(), self->GetThreadId(), 0u, nullptr);
        return false;  // Failure.
        // 瘦锁开释
        case LockWord::kThinLocked: {
        uint32_t thread_id = self->GetThreadId();
        uint32_t owner_thread_id = lock_word.ThinLockOwner();
        假如当时持有锁的线程跟想要开释锁的线程不是同一个,这也是一种过错
        if (owner_thread_id != thread_id) {
        FailedUnlock(h_obj.Get(), thread_id, owner_thread_id, nullptr);
        return false;  // Failure.
        } else {
        // We own the lock, decrease the recursion count.
        LockWord new_lw = LockWord::Default();
        瘦锁开释便是直接让ThinLockCount-1
        if (lock_word.ThinLockCount() != 0) {
        uint32_t new_count = lock_word.ThinLockCount;
        new_lw = LockWord::FromThinLockId(thread_id, new_count, lock_word.GCState());
        } else {
        new_lw = LockWord::FromDefault(lock_word.GCState());
        }
        if (!gUseReadBarrier) {
        DCHECK_EQ(new_lw.ReadBarrierState(), 0U);
        h_obj->SetLockWord(new_lw, true);
        AtraceMonitorUnlock();
        // Success!
        return true;
        } else {
        // Use CAS to preserve the read barrier state.
        if (h_obj->CasLockWord(lock_word, new_lw, CASMode::kWeak, std::memory_order_release)) {
        AtraceMonitorUnlock();
        // Success!
        return true;
        }
        }
        continue;  // Go again.
        }
        }
        胖锁便是直接调用Unlock办法
        case LockWord::kFatLocked: {
        Monitor* mon = lock_word.FatLockMonitor();
        return mon->Unlock(self);
        }
default: {
        LOG(FATAL) << "Invalid monitor state " << lock_word.GetState();
        UNREACHABLE();
        }
        }
        }
        }

MonitorExit比较简略,便是直接按照分配逻辑进行开释,首先判别了当时锁状况是否正确,比方kUnlocked状况就不能开释,瘦锁便是lockcount-1即可,胖锁则调用Unlock办法。

总结

经过对native层的线程同步的理解,咱们理解了Monitor,LockWord,Object之间的联系,也就能理解虚拟机侧供给的各种获取锁联系的api的内部为什么会这么完成了。