用户空间锁-2-虚拟机中对象锁synchronized/wait-notify实现分析
前言
编程过程中经常会遇到线程的同步问题,Java 中对同步问题的解决方案比较多(synchronized、JUC、原子操作、volatile、条件变量等),其中 synchronized 最方便、简单易用,也是 java 编程中使用最多的临界区保护方案。本文主要讲述对象锁的相关知识,详细介绍synchronized 和Object 的关键方法的虚拟机实现原理。
相关文件:
/art/runtime/monitor.cc
kernel/msm-4.14/kernel/futex.c
一、Java 对象锁的使用方式
1. 实例方法的同步
public synchronized void method_need_sync() { //临界区代码 }
synchronized 修饰实例方法,该同步仅对当前对象的该方法起作用,同一时间只能有一个线程可以进入该对象的此方法。对于不同对象的此函数,不起互斥保护作用。
2. 静态方法的同步
public static synchronized void static_method_need_sync() { //临界区代码 }
synchronized 修饰静态方法,该同步对当前类对象的该方法起作用,同一时间只能有一个线程可以进入该方法。
3. 代码块的同步
public void codeblock_need_sync() { synchronized(object) { //可使用成员变量object=new Object()或this //临界区代码 } }
在大多少情况下,并不需要对整个方法进行保护,当 synchronized 修饰代码块时,该代码块的访问依赖于 object 对象锁的互斥访问,同一时间只能有一个线程持有object 对象锁。
更准确的来讲,synchronized 关键字是依赖于对象锁而生效的,每个 synchronized 同步块开始的地方都会生成 monitor-enter obj 指令,同步块结束的地方生成 monitor-exit obj 的指令,其中 obj 为用于控制互斥访问的对象。同一时间只能有一个线程持有 obj 的对象锁。
在小节1中 synchronized 依赖的是实列对象,小节2中 synchronized 依赖的是类对象,小节3中 synchronized 依赖的是 object 对象。
当一个对象object控制多个代码块时(比如通过主线程中的一个对象进行互斥),多个代码块也是互斥访问的,如下面代码:
4 Object的 wait() 和 notify()方法使用
Object 作为所有类的基类,因此所有的类都实现了这些方法。典型的用法如下:
首先 thread-1 持有 object 对象锁,并调用 object.wait() 方法后,则该线程进入 WAITING 状态,并释放 object 对象锁,等待其它线程来唤醒它。当 thread-2 持有 object 对象锁,并调用 object.notify() 方法后,唤醒 thread-1,thread-1 重新获得 object 对象锁继续执行。
Object类方法说明:
二、Android对象内存结构
1. 对象内存结构
一个类的实例对象内存主要由3部分组成:
(1) 对象头:对象头包括 kclass_ 和 monitor_ 两个字段,其中 kclass_ 存放指向类对象的指针,通过该指针可以找到该对象对应的类,monitor_ 用于存放对象运行时的标识数据,例如: GC 标志位、哈希码、锁状态等信息,后面详细分析。对象头结构定义在 art/runtime/mirror/object.h 中。
(2) 实例数据,该部分存放实例变量值,父类实例变量值在前,子类在后,且实例变量值按照如下顺序进行排序:
//android/art/runtime/class_linker.cc /* 我们使用以下字段类型顺序来分配偏移量。某些字段可以向前移动以填补空白,请参阅 ClassLinker::LinkFields()。*/ enum class ClassLinker::LinkFieldsHelper::FieldTypeOrder : uint16_t { kReference = 0u, kLong, kDouble, kInt, kFloat, kChar, kShort, kBoolean, kByte, kLast64BitType = kDouble, kLast32BitType = kFloat, kLast16BitType = kShort, };
(3) 对齐填充,对象在内存中是按照 8byte 对齐的,如果实例数据部分没有按照 8byte 对齐,则填充为 8byte 对齐。
2. monitor_ 字段分析
monitor_ 字段定义在 art/runtime/mirror/object.h,类型为 uint32_t,主要有下面3个操作函数:
LockWord GetLockWord(bool as_volatile) REQUIRES_SHARED(Locks::mutator_lock_); void SetLockWord(LockWord new_val, bool as_volatile) REQUIRES_SHARED(Locks::mutator_lock_); bool CasLockWord(LockWord old_val, LockWord new_val, CASMode mode, std::memory_order memory_order) REQUIRES_SHARED(Locks::mutator_lock_);
注: 这些函数实现在 android/art/runtime/mirror/object-readbarrier-inl.h 中。
操作函数中 SetLockWord() 和 CasLockWord() 函数的入参或 GetLockWord() 函数的返回值都包含 LockWord 变量,对 monitor_ 字段的操作是通过 LockWord::value_ 的值进行的。//TODO: monitor_成员变量是不是就是锁字?
下面再来看 LockWord 定义:
LockWord 类的定义在 art/runtime/lock_word.h 文件中,从注释中可以看到 LockWord 的使用主要有4种状态,如下:
下面对 LockWord 各状态进行详细说明:
(1) unlocked/thin 状态下31-30 bit 为 00,默认状态下为 unlocked 状态,当对象进行线程同步时变成 thin-lock 状态,27-16bit 记录了 thin-lock 重入的次数,15-0 bit 记录了持有该 thin-lock 的线程ID。
(2) fat-lock 状态下 31-30 bit 为 01,当对象锁在 thin-lock 状态,且有新的(非owner)线程与其竞争,经过适当的等待期(sched_yield 调用、循环获取 thin-lock 状态)后依然无法拿到锁,则转换为 fat-lock 状态,并为该对象分配一个 Monitor 资源。变成 fat-lock 后才有 Monitor 与其对应。
(3) hash-state 状态下 31-30 bit 为 10,在 27-0 bit 存储对象的 hash-code,当在其它模式下,hash-code 会存储在该对象关联的 Monitor 对象中。
(4) forwarding-address-state 状态下 31-30 bit 为 11,在 concurrent copying GC 的copy 阶段,当一个对象被拷贝后,指向拷贝后的对象地址,当线程访问到该对象后,通过该转发地址,访问新的对象。
第28 位为 read-barrier-bit,如果对象 LockWorkd 的该bit 被设置,则在访问该对象的成员时会进入慢速路径,判断对象是不是需要更新,如果需要更新,则返回拷贝后的对象地址。
第29 位为 mark-bit,通过该bit位可以快速判断是否标记过,避免重复标记。
三、对象锁代码分析
1.首先我们看一段代码
public class TestDemo { public static void main(String[] args) { Object obj = new Object(); new Thread(new Runnable(){ @Override public void run() { synchronized(obj) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } obj.notify(); } } }).start(); synchronized(obj) { System.out.println("test start"); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test end"); } } }
这段代码比较简单,主要有下面两个核心点:
(1) 在主线程执行的过程中,用 obj 对象进行线程同步,并调用 obj.wait() 函数,使线程阻塞在了 obj 对象锁上等待唤醒。
(2) main函数中创建匿名线程,该线程首先sleep 2000ms,然后唤醒阻塞在 obj 对象锁上线程。
2. 编译TestDemo.java命令如下:
(1) Javac 将 TestDemo.java 文件编译生成 TestDemo*.class 文件,java 编译过程中每个类会生成一个class 文件。
(2) d8 命令将 TestDemo*.class 文件通过编译、重构、重排、压缩、混淆后生成对应的 dex (Dalvik Executable file)格式文件。
(3) dexdump.exe 命令可以查看 dex 文件格式的详细信息,如校验信息、dex 头信息、生成 dex 的 CFG 信息、dex 的反汇编信息等,详细使用方法可以通过 dexdump.exe –help 命令查看
通过 dexdump.exe –d classes.dex 查看反汇编,其中 run 方法指令信息如下:
main 函数的指令信息如下:
对部分指令解析如下:
对于 dex 指令详细格式可以阅读google 的官方文档:https://source.android.com/docs/core/runtime/dalvik-bytecode
注:看起来 monitor-enter 对应的机器码应该是 1d0X 其中X是后面跟的v的下标。
本文重点分析 monitor-enter、monitor-exit、Object.wait()、Object.notify()在虚拟机中的详细实现。
3. Object.wait() 流程分析
Object.wait() 的调用关系如下:
Object 类是所有类的父类,任何类中都可以调用 public 的 wait() 方法,最终调用到虚拟机的 monitor.cc 文件的有6个参数的 wait() 静态方法.
//art/runtime/monitor.cc void Monitor::Wait(Thread* self, ObjPtr<mirror::Object> obj, int64_t ms, int32_t ns, bool interruptShouldThrow, ThreadState why) { StackHandleScope<1> hs(self); Handle<mirror::Object> h_obj(hs.NewHandle(obj)); Runtime::Current()->GetRuntimeCallbacks()->ObjectWaitStart(h_obj, ms); if (UNLIKELY(self->ObserveAsyncException() || self->IsExceptionPending())) { return; } LockWord lock_word = h_obj->GetLockWord(true); /* 根据最高2bit state字段的值判断当前状态 */ while (lock_word.GetState() != LockWord::kFatLocked) { switch (lock_word.GetState()) { case LockWord::kHashCode: // Fall-through. case LockWord::kUnlocked: ThrowIllegalMonitorStateExceptionF("object not locked by thread before wait()"); return; // Failure. case LockWord::kThinLocked: { uint32_t thread_id = self->GetThreadId(); //这个不是tid,只是锁实现中使用的一个id, GetTid()返回的才是tid. uint32_t owner_thread_id = lock_word.ThinLockOwner(); //返回 value_ 的低16bit thread id owner 字段 if (owner_thread_id != thread_id) { ThrowIllegalMonitorStateExceptionF("object not locked by thread before wait()"); return; // Failure. } else { Inflate(self, self, h_obj.Get(), 0); //主要路径 lock_word = h_obj->GetLockWord(true); } break; } case LockWord::kFatLocked: // Unreachable given the loop condition above. Fall-through. default: { LOG(FATAL) << "Invalid monitor state " << lock_word.GetState(); UNREACHABLE(); } } } Monitor* mon = lock_word.FatLockMonitor(); /* 调用5个参数的monitor->wait() */ mon->Wait(self, ms, ns, interruptShouldThrow, why); }
首先构造了一个操作 obj 的 Handle 对象 h_obj,通过 ObjectWaitStart() 函数通知 jvmti 调试系统发生了 JVMTI_EVENT_MONITOR_WAIT 事件。
JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口,可以用来开发并监控虚拟机,可以查看JVM内部的状态,并控制JVM应用程序的执行。可实现的功能包括但不限于:调试、监控、线程分析、覆盖率分析工具等。
首先获得 h_obj 对象的 LockWord 字段,lock_word.GetState() 函数获得当前的锁状态,主要有下面几种情况:
(1) hash 或 unlocked 状态:
因为调用 wait() 方法必须持有对象锁,即需要用 synchronized 包裹住Object.wait(),所以不会出现这两种状态,如果出现则抛出 IllegalMonitorStateException 异常。
(2) thin lock 状态:
当持有该对象锁的线程不是要 wait 的线程,也抛出 IllegalMonitorStateException 异常,当持有锁的线程与要 wait 的线程一致,这时需要将 thin-lock inflate 为 fat -lock,inflate 的过程在下面 monitor-enter 指令分析中分析。
当对象锁 inflate 为 fat-lock 状态后,调用 Monitor 对象的实例方法 Wait 让线程进入 sleep 状态等待。
3.1 Monitor::Wait() — Android-Q上实现
void Monitor::Wait(Thread* self, ObjPtr<mirror::Object> obj, int64_t ms, int32_t ns, bool interruptShouldThrow, ThreadState why) { //monitor.cc DCHECK(self != nullptr); DCHECK(obj != nullptr); StackHandleScope<1> hs(self); Handle<mirror::Object> h_obj(hs.NewHandle(obj)); LockWord lock_word = h_obj->GetLockWord(true); /* Fat lock应该是竞争锁,think lock应该是重入锁 */ while (lock_word.GetState() != LockWord::kFatLocked) { switch (lock_word.GetState()) { /* * 1). hash 或 unlocked 状态: * 因为调用wait()方法必须持有对象锁,所以不会出现这两种状态,如果出现则抛 * 出 IllegalMonitorStateException 异常。 */ case LockWord::kHashCode: // Fall-through. case LockWord::kUnlocked: ThrowIllegalMonitorStateExceptionF("object not locked by thread before wait()"); return; // Failure. /* * 2).thin lock 状态: * 当持有该对象锁的线程不是要 wait 的线程,也抛出 IllegalMonitorStateException 异常,当持 * 有锁的线程与要 wait 的线程一致,这时需要将thin lock inflate 为fat lock,inflate 的过程 * 在 monitor-enter 指令分析中分析。 * 当对象锁inflate 为fat lock 状态后,调用Monitor 对象的实例方法Wait让线程进入sleep 状态等 * 待。 */ case LockWord::kThinLocked: { /* 这个不是pid,只是锁实现中使用的一个id */ uint32_t thread_id = self->GetThreadId(); /* 返回 value_ 的低16bit thread id owner 字段 */ uint32_t owner_thread_id = lock_word.ThinLockOwner(); if (owner_thread_id != thread_id) { ThrowIllegalMonitorStateExceptionF("object not locked by thread before wait()"); return; // Failure. } else { /*我们拥有锁,膨胀以使我们自己在监视器上排队。可能会虚假失败,因此重新加载。 */ Inflate(self, self, h_obj.Get(), 0); lock_word = h_obj->GetLockWord(true); } break; } case LockWord::kFatLocked: // Unreachable given the loop condition above. Fall-through. default: { LOG(FATAL) << "Invalid monitor state " << lock_word.GetState(); UNREACHABLE(); } } } Monitor* mon = lock_word.FatLockMonitor(); /* 调用5个参数的Wait() */ mon->Wait(self, ms, ns, interruptShouldThrow, why); }
下面看5个参数的Monitor::Wait():
void Monitor::Wait(Thread* self, int64_t ms, int32_t ns, bool interruptShouldThrow, ThreadState why) { /* Mutex类型的变量,Lock中又直接调用了 Mutex::ExclusiveLock(),此函数中使用了futex() */ monitor_lock_.Lock(self); // Make sure that we hold the lock. if (owner_ != self) { monitor_lock_.Unlock(self); ThrowIllegalMonitorStateExceptionF("object not locked by thread before wait()"); return; } /* 释放我们的控制 - 即使我们处于递归锁的深层几级中,我们也需要放手,并且我们需要稍后恢复它。*/ int prev_lock_count = lock_count_; lock_count_ = 0; owner_ = nullptr; ArtMethod* saved_method = locking_method_; locking_method_ = nullptr; uintptr_t saved_dex_pc = locking_dex_pc_; locking_dex_pc_ = 0; bool was_interrupted = false; bool timed_out = false; /* 就是持有 Thread::wait_mutex_ 锁,然后等待 Thread::wait_cond_ 条件变量 */ { /* 更新线程状态。如果 GC 醒来,它会忽略我们,知道我们不会触及此状态下的任何引用,并且我 * 们将在转换之前检查挂起模式。*/ ScopedThreadSuspension sts(self, why); /* 构造函数中是持有锁,析构函数中是释放锁, 里面是基于Mutex实现的 */ MutexLock mu(self, *self->GetWaitMutex()); //返回 Thread::wait_mutex_ 成员,它是在Thread的构造方法中赋值的 /* * 将我们自己添加到在此监视器上等待的线程集中。 重要的是,我们仅在获取 GetWaitMutex 后才添加 * 到等待集中,以便在释放 Monitor_lock_ 之后发生的对 Notify() 的调用不会将我们从 wait_set_ 移 * 动到 wake_set_,直到我们向该监视器上的竞争者发出信号。 */ AppendToWaitSet(self); ++num_waiters_; /* 将 wait_monitor_ 设置为我们将等待的监视器对象。当 wait_monitor_ 为非空时,通知或中断线 * 程必须向线程的 wait_cond_ 发出信号以将其唤醒。 */ DCHECK(self->GetWaitMonitor() == nullptr); self->SetWaitMonitor(this); //设置到 Thread::wait_monitor_ 中 /* 解除监视器锁 */ SignalContendersAndReleaseMonitorLock(self); //TODO: wait前为啥执行唤醒动作呢? /* 处理在我们调用 wait() 之前线程被中断的情况。*/ if (self->IsInterrupted()) { was_interrupted = true; } else { /* 等待通知或超时发生。*/ if (why == kWaiting) { /* 这个 wait-notify 和 Synchronized 使用的不是同一个条件变量。这里使用的是 Thread::wait_cond_, * 而后者使用的是 Monitor::monitor_contenders_, 两个逻辑互不影响。 */ self->GetWaitConditionVariable()->Wait(self); //返回条件变量成员 wait_cond_ 也是在Thread的构造函数中赋值的 } else { DCHECK(why == kTimedWaiting || why == kSleeping) << why; timed_out = self->GetWaitConditionVariable()->TimedWait(self, ms, ns); } was_interrupted = self->IsInterrupted(); } } /* 这里又将owner设置为self了 */ owner_ = self; lock_count_ = prev_lock_count; locking_method_ = saved_method; locking_dex_pc_ = saved_dex_pc; --num_waiters_; RemoveFromWaitSet(self); monitor_lock_.Unlock(self); }
4. Object.notify() 流程分析
这里我们直接分析 DoNotify 函数:
void Monitor::DoNotify(Thread* self, ObjPtr<mirror::Object> obj, bool notify_all) { LockWord lock_word = obj->GetLockWord(true); //获得当前obj对象的锁, 里面有 value_ 也即是对象头中的 monitor_ 成员 switch (lock_word.GetState()) { //获取当前锁状态 case LockWord::kHashCode: // Fall-through. case LockWord::kUnlocked: ThrowIllegalMonitorStateExceptionF("object not locked by thread before notify()"); return; // Failure. case LockWord::kThinLocked: { uint32_t thread_id = self->GetThreadId(); uint32_t owner_thread_id = lock_word.ThinLockOwner(); if (owner_thread_id != thread_id) { ThrowIllegalMonitorStateExceptionF("object not locked by thread before notify()"); return; // Failure. } else { // We own the lock but there's no Monitor and therefore no waiters. return; // Success. } } case LockWord::kFatLocked: { Monitor* mon = lock_word.FatLockMonitor(); //根据低28bit的monitor-id找到对应的monitor if (notify_all) { //notify和notifyall的区别 mon->NotifyAll(self); } else { mon->Notify(self); //单纯的wait-notify走这里 } return; // Success. } default: { LOG(FATAL) << "Invalid monitor state " << lock_word.GetState(); UNREACHABLE(); } } }
通过 lock_word.GetState() 获得当前 obj 对象的锁状态,主要有下面情况:
(1) hash 或 unlocked 状态:
Object.notify() 的时候也是需要持对象锁的,因此不应该是这种状态,于是抛出 IllegalMonitorStateException 异常。
(2) thin-lock 状态:
当持有该对象锁的线程不是要 notify 的线程,也抛出 IllegalMonitorStateException 异常,当持有锁的线程与要notify 的线程一致,这时说明没有需要通知唤醒的线程,直接返回。
(3) fat-lock 状态:
在 Object.notify() 流程中参数 notify_all 为 false,则直接调用 mon->Notify(self); 通知唤醒等待线程。
5. monitor-enter 流程分析
5.1 对于解释执行和机器码执行模式,最终都会调用到 Object 对象的 MonitorEnter 函数。
//art/runtime/mirror/object-inl.h inline ObjPtr<mirror::Object> Object::MonitorEnter(Thread* self) { return Monitor::MonitorEnter(self, this, /*trylock=*/false); }
5.2 下面来分析Monitor类的静态方法 MonitorEnter 函数。
ObjPtr<mirror::Object> Monitor::MonitorEnter(Thread* self, ObjPtr<mirror::Object> obj, bool trylock) { self->AssertThreadSuspensionIsAllowable(); //名字: 断言线程暂停是允许的 /* FakeLock 主要用于线程安全性检查,主要在编译期检测 */ obj = FakeLock(obj); uint32_t thread_id = self->GetThreadId(); size_t contention_count = 0; StackHandleScope<1> hs(self); Handle<mirror::Object> h_obj(hs.NewHandle(obj)); while (true) { LockWord lock_word = h_obj->GetLockWord(false); //获取锁状态 switch (lock_word.GetState()) { /* * 锁状态为 unlocked 状态时,转换为 thin lock 状态,并通过 cas 操作更新lock count。 * 若是unlocked的状态,即除了最高2bit其它全是0,则此时只将 thread_id 设置进去。 */ case LockWord::kUnlocked: { LockWord thin_locked(LockWord::FromThinLockId(thread_id, 0, lock_word.GCState())); /* 目前值和旧值相等则更新为新值并返回true,否则返回false */ if (h_obj->CasLockWord(lock_word, thin_locked, CASMode::kWeak, std::memory_order_acquire)) { return h_obj.Get(); //Success! } continue; //Go again. } /* * 当锁状态为thin lock 状态时,首先获取锁的 owner 线程id,如果 owner id 与竞争线程id一致, * 则有下面两种情况: * (1) 如果lock count加1小于等于(1<<12)-1==4095时,将 lock count+1 更新lock count。 * (2) 如果lock count加1大于(1<<12)-1时(lock count 区域无法存储),则调用InflateThinLocked 函数 * 对thin lock 进行膨胀。 */ case LockWord::kThinLocked: { uint32_t owner_thread_id = lock_word.ThinLockOwner(); /* 若竞争的线程和持锁线程的id一致,对应重入持锁场景 */ if (owner_thread_id == thread_id) { /* 将thin-lock count字段加1 */ uint32_t new_count = lock_word.ThinLockCount() + 1; /* thin-lock count最大值是4095, 一般不会超,若超了膨胀为fat-lock去阻塞继续嵌套持锁 */ if (LIKELY(new_count <= LockWord::kThinLockMaxCount)) { /* 构建一个临时LockWord对象 */ LockWord thin_locked(LockWord::FromThinLockId(thread_id, new_count, lock_word.GCState())); /* 通过CAS操作对thin-lock的计数加1,成功加1返回true.*/ if (h_obj->CasLockWord(lock_word, thin_locked, CASMode::kWeak, std::memory_order_relaxed)) { return h_obj.Get(); // Success! } continue; //Go again. } else { /* 如果 thin lock计数溢出了,即大于4095了,则膨胀为胖锁, 膨胀时才分配一个Monitor锁与对象关联起来 */ InflateThinLocked(self, h_obj, lock_word, 0); } } else { /* 当前线程去持锁,发现持thin-lock锁的线程不是当前线程,就是出现了锁竞争 */ /* 若用户只是try-lock,有竞争则失败返回 */ if (trylock) { return nullptr; } contention_count++; Runtime* runtime = Runtime::Current(); /* * 执行50次的sched_yield() 后还未获得锁资源,则将thin-lock膨胀为fat-lock。 * 实测即使CPU高负载71us内也能执行完52次yield操作。 */ if (contention_count <= runtime->GetMaxSpinsBeforeThinLockInflation()) { //50 sched_yield(); } else { contention_count = 0; /* 先变为fat-lock,下一轮循环就走到fat-lock处理路径中去了 */ InflateThinLocked(self, h_obj, lock_word, 0); } } continue; } /* * 当锁状态已经是fat lock 状态,通过 lock_word.FatLockMonitor() 获取 Monitor 对象,并通过 Monitor * 对象的Lock函数让线程进入等待状态。 */ case LockWord::kFatLocked: { /* 取低28bit对应的mon_id对应的Monitor结构 */ Monitor* mon = lock_word.FatLockMonitor(); if (trylock) { return mon->TryLock(self) ? h_obj.Get() : nullptr; } else { /* 这里提供竞争阻塞逻辑 */ mon->Lock(self); return h_obj.Get(); //表示成功获取锁返回 } } /* 当锁状态已经是hash 状态时,直接对锁进行膨胀 */ case LockWord::kHashCode: Inflate(self, nullptr, h_obj.Get(), lock_word.GetHashCode()); continue; default: { LOG(FATAL) << "Invalid monitor state " << lock_word.GetState(); UNREACHABLE(); } } } }
可以看到 thin lock 的膨胀有两种情形:
(1) lock count 的值超过了4095,嵌套持锁深度太深了,这时锁的 owner 为当前线程,即直接通过 Inflate() 函数膨胀。I: 也即嵌套深度过深,也可能会休眠。
(2) 锁的 owner 不是当前线程,出现了竞争持锁的情况,则通过 SuspendThreadByThreadId 暂停锁的 owner 线程(主要是 owner 线程和锁膨胀线程都需要访问对象的 LockWord,避免竞态问题),然后通过 Inflate 进行膨胀。膨胀完成后再唤醒锁的 owner 线程。
5.3 再看 Inflate 的过程:
void Monitor::Inflate(Thread* self, Thread* owner, ObjPtr<mirror::Object> obj, int32_t hash_code) { /* * 分配获取一个 Monitor 的对象 m, 并通过 m->install(self) 函数更新对象的 LockWord 字段,这时 LockWord * 字段信息包含 fat-lock 状态、GC 状态、MonitorId,然后将 m 保存在对象的 monitor_list_ 成员中。 */ Monitor* m = MonitorPool::CreateMonitor(self, owner, obj, hash_code); if (m->Install(self)) { Runtime::Current()->GetMonitorList()->Add(m); } else { MonitorPool::ReleaseMonitor(self, m); } }
monitor_list_ 中存储了当前虚拟机使用的所有 Monitor 对象。在 GC 的过程中,通过该链表,访问到 Monitor 依赖的对象。如果对象变成垃圾对象,则回收该 Monitor,否则更新 Monitor 依赖的对象信息。
MonitorId 用于唯一标识一个 Monitor,生成的方法可以看 monitor_pool.h 中的实现。
5.4 再看 Monitor::Lock 的过程 —— Android-S上实现:
void Monitor::Lock(Thread* self) { /* 这里传spin=true, 会连续6次尝试 ExclusiveTryLock() 若成功持锁,将 Monitor::owner_ 设置为self并返回true.*/ if (TryLock(self, /*spin=*/ true)) { return; } Thread *orig_owner = nullptr; /* 将等待者计数加1 */ size_t num_waiters = num_waiters_.fetch_add(1, std::memory_order_relaxed); /* 只有使能了trace打印并且owner不为空才打印,owner若为空可以直接持锁,不用阻塞休眠了 */ if (ATraceEnabled() && owner_.load(std::memory_order_relaxed) != nullptr) { /* * thread_list_lock_ 是Locks类的静态成员,即每进程的一个的全局锁,用来保护锁自身实现逻辑, * 也可能进入这里面的futex休眠 * 从这里可以看出,这个函数返回后必须是成功持锁的。 */ Locks::thread_list_lock_->ExclusiveLock(self); /* owner_ 成员保存的是当前持锁的owner */ orig_owner = owner_.load(std::memory_order_relaxed); /* 若之前的持续的线程还在临界区中,还没释放锁,则打印一个monitor锁阻塞的trace */ if (orig_owner != nullptr) { /* 这个不是线程的tid, Thread::Init()中初始化为i+1, Android12上不是,但是Android10上的是tid */ const uint32_t orig_owner_thread_id = orig_owner->GetThreadId(); //return tls32_.thin_lock_thread_id; /* (1) trace打印目前正在临界区持锁的orig_owner的信息 */ orig_owner->GetThreadName(name); /* 这里面有一部分trace信息打印,"monitor contention with owner ..." */ oss << PrettyContentionInfo(name, orig_owner_thread_id, owners_method, owners_dex_pc, num_waiters); /* 这个全局锁是为了保护monitor锁本身的实现机制,应该主要是为了保护owner_成员 */ Locks::thread_list_lock_->ExclusiveUnlock(self); /* (2) trace打印这个当前竞争者的信息 */ ArtMethod* m = self->GetCurrentMethod(&pc); /* 这个函数中根据 ArtMethod 来来获取文件名和行号 */ TranslateLocation(m, pc, &filename, &line_number); /* * trace打印内容: * <...>-3207 ( 2848) [002] .... 189.768870: tracing_mark_write: B|2848|monitor contention with * owner Binder:2848_4 (34) at void android.os.MessageQueue.nativeWake(long)(MessageQueue.java:-2) * waiters=0 blocking from android.os.Message android.os.MessageQueue.next()(MessageQueue.java:360) * * 这里只是打印出“blocking from”后面的内容,即: * blocking from android.os.Message android.os.MessageQueue.next()(MessageQueue.java:360) */ oss << " blocking from " << ArtMethod::PrettyMethod(m) << "(" << (filename != nullptr ? filename : "null") << ":" << line_number << ")"; /* 这里开始打印 */ ATraceBegin(oss.str().c_str()); started_trace = true; } } /* 将monitor和Object对象绑定,然后进入休眠 */ self->SetMonitorEnterObject(GetObject().Ptr()); //tlsPtr_.monitor_enter_object = obj; /* 这里是锁竞争获取这个自己对象的这个 monitor_lock_, 这里会调用futex进入内核休眠 */ monitor_lock_.ExclusiveLock(self); /* 走到这里,当前线程就已经持有 monitor_lock_ 了,将自己设置为owner, 这里的owner不是tid,而是Thread对象 */ owner_.store(self, std::memory_order_relaxed); /* 唤醒获得CPU资源后,结束trace打印 */ if (started_trace) { ATraceEnd(); } /* 将monitor和Object解绑 */ self->SetMonitorEnterObject(nullptr); //tlsPtr_.monitor_enter_object = nullptr; /* 将waiters统计计数减去1 */ num_waiters_.fetch_sub(1, std::memory_order_relaxed); }
可以看到有一个每进程全局锁 Locks::thread_list_lock_ 用来保护锁实现逻辑,它本身也会调用futex()使进程进入休眠,当前monitor的阻塞逻辑是在 monitor_lock_.ExclusiveLock(self) 中实现的,####### 它里面会调用 futex()系统调用进入休眠。
注意这里的trace "monitor contention with" 打印是出来的tid并不是内核中的tid,而是art中的一个线程id序号。
下面看 Mutex::monitor_lock_.ExclusiveLock() ——Android-S上实现:
void Mutex::ExclusiveLock(Thread* self) { /* 若不允许递归持锁或没有递归持锁,才执行后续逻辑 */ if (!recursive_ || !IsExclusiveHeld(self)) { bool done = false; do { /* 锁状态字当前的值, bit0表示是否持锁,其它bit表示竞争者的数量 */ int32_t cur_state = state_and_contenders_.load(std::memory_order_relaxed); /* 若锁的bit0为0表示没有被持有,则将bit0置为1,若锁当前没有被持有,则成功持锁后退出 */ if (LIKELY((cur_state & kHeldMask) == 0) /* lock not held */) { /* 让锁字变为已持锁状态 */ done = state_and_contenders_.CompareAndSetWeakAcquire(cur_state, cur_state | kHeldMask); } else { /* * 走到这里说明锁状态字bit0是1已经是持锁状态了,出现了竞争,要挂起休眠了。 * * 构造函数和析构函数中会打印"Lock contention on XX lock (owner tid: YY)"这样的trace打印, 此处的tid是真正的tid */ ScopedContentionRecorder scr(this, SafeGetTid(self), GetExclusiveOwnerTid()); /* * 这里会自旋一小段时间(只使用了自旋),直到释放锁(参数三预测成功) 退出或休眠超时退出。 * 自旋和休眠的原因:"根据经验,每次循环时再次自旋似乎很重要;如果我们对休眠和唤醒感到无聊,我们应该坚持不懈地尝试持锁“ */ if (!WaitBrieflyFor(&state_and_contenders_, self, [](int32_t v) { return (v & kHeldMask) == 0; })) { /* * 进来了就表示经过一些列自旋/休眠等待后,锁还没有被释放,则要进入休眠等待状态了。 * state_and_contenders_ += 2 在竞争者计数位端加1 */ increment_contenders(); /* 这个副本的竞争者计数位端也加1 */ cur_state += kContenderIncrement; /* 记录开始被futex阻塞休眠的时间 */ uint64_t wait_start_ms = enable_monitor_timeout_ ? MilliTime() : 0; do { timespec timeout_ts; timeout_ts.tv_sec = 0; timeout_ts.tv_nsec = Runtime::Current()->GetMonitorTimeoutNs(); /* 调用futex进入休眠, futex子是state_and_contenders_, 注意,futex休眠可能会失败 */ if (futex(state_and_contenders_.Address(), FUTEX_WAIT_PRIVATE, cur_state, enable_monitor_timeout_ ? &timeout_ts : nullptr , nullptr, 0) != 0) { /* * 我们只是在增加和竞争者并检查锁是否仍然由其他人持有之后才去睡觉。 EAGAIN 和 EINTR 均表示虚假失败,会从头重试。 * 所有的报错都没有进行额外处理,只是这些错误码打印了一下log和栈回溯而已。 */ if ((errno != EAGAIN) && (errno != EINTR)) { if (errno == ETIMEDOUT) { try_times++; if (try_times <= kMonitorTimeoutTryMax) { //5 只dump 5次 /* 这里还可以进行栈回溯! 里面会dump出来等待的时间 */ DumpStack(self, wait_start_ms, try_times); } } else { PLOG(FATAL) << "futex wait failed for " << name_; } } } /* runtime若是挂了的话就在这里休眠 */ SleepIfRuntimeDeleted(self); /* 获取锁字状态,重新进行尝试,直到锁被持有(也可能被其它线程持有)。 */ cur_state = state_and_contenders_.load(std::memory_order_relaxed); } while ((cur_state & kHeldMask) != 0); /* state_and_contenders_ -= 2 在竞争者计数位减去1 */ decrement_contenders(); } } /* * 直到被线程成功持锁为止,否则不会退出这个循环。这里可能重复执行,比如唤醒后锁又被别人争走了, * 因此 "Lock contention on XX" trace可能重复打印。 */ } while (!done); /* 成功持锁后将自己的tid设置为锁线程owner的tid */ exclusive_owner_.store(SafeGetTid(self), std::memory_order_relaxed); }
可以看到此函数只有自己成功持锁了才会返回,否则不会返回。
5.5 再看 Monitor::Lock 的过程 —— Android-Q上实现:
//art/runtime/monitor.cc /* 参数self应该是对当前被阻塞线程的描述 */ void Monitor::Lock(Thread* self) { /* * Monitor 是在 art::Mutex 的基础上构建起来的,此 Mutex 类型的成员用来保护 Monitor 的实现机制, * 这里保护 num_waiters_ 和 owner_ 成员。 * 它直接调用了 Mutex::ExclusiveLock(), 这个函数也会调用futex()进入休眠等待。 */ monitor_lock_.Lock(self); while (true) { /* 先尝试持锁,若成功,退出循环 */ if (TryLockLocked(self)) { break; } /* 尝试持锁失败,走到这里就进入了竞争持锁流程 */ /* 先保存一个副本,然后对等待者计数成员加1, 这里的这个 num_waiters 是没有包含自己的 */ size_t num_waiters = num_waiters_; ++num_waiters_; /* * 打印内容: * StackTraceColle-1770 (1628) [002] .... 189.492078: tracing_mark_write: B|1628|monitor contention with owner main (1) * at boolean android.os.MessageQueue.enqueueMessage(android.os.Message, long)(MessageQueue.java:578) waiters=0 blocking from * android.os.Message android.os.MessageQueue.next()(MessageQueue.java:360) * 这里已经打印出了持锁owner的信息 */ if (ATraceEnabled()) { /* 若当前锁被持有着还没释放,则trace打印持锁信息 */ if (owner_ != nullptr) { std::ostringstream oss; std::string name; /* name是锁owner线程的名字comm */ owner_->GetThreadName(name); /* (1) 打印持锁者和waiter个数信息,注: 这里的参数2的确是tid了,参数5 waiters不包含当前竞争者 */ oss << PrettyContentionInfo(name, owner_->GetTid(), owners_method, owners_dex_pc, num_waiters); /* (2) 打印当前这个竞争者线程卡在哪里了 */ ArtMethod* m = self->GetCurrentMethod(&pc); const char* filename; int32_t line_number; TranslateLocation(m, pc, &filename, &line_number); /* 从这里开始就是当前竞争者的信息了 */ oss << " blocking from " << ArtMethod::PrettyMethod(m) << "(" << (filename != nullptr ? filename : "null") << ":" << line_number << ")"; /* 开始打印trace的地方 */ ATraceBegin(oss.str().c_str()); started_trace = true; } } monitor_lock_.Unlock(self); /* 将Monitor和Object对象绑定 */ self->SetMonitorEnterObject(GetObject().Ptr()); { /* 改为 blocked,放弃 mutator_lock_。 */ ScopedThreadSuspension tsc(self, kBlocked); // Change to blocked and give up mutator_lock_. { /* 利用构造和析构函数调用 monitor_lock_.ExclusiveLock(self); 和 monitor_lock_.ExclusiveUnlock(self) */ MutexLock mu2(self, monitor_lock_); /* 锁的持有者还是没有释放锁 */ if (owner_ != nullptr) { /* A10上使当前进程休眠的主要是这个条件变量的wait(), 这和A12是有区别的 */ monitor_contenders_.Wait(self); } } } /* 结束trace打印,此时它已经从休眠中退出了 */ if (started_trace) { ATraceEnd(); } /* Monitor与Object取消关联 */ self->SetMonitorEnterObject(nullptr); /* 保护成员 num_waiters_ */ monitor_lock_.Lock(self); --num_waiters_; } monitor_lock_.Unlock(self); }
其中 monitor_lock_ 是 Monitor 类的 Mutex monitor_lock_; 成员,看起来是用于保护内部实现。monitor_contenders_ 是定义在 monitor.h 中的 Monitor 类的 MoniConditionVariable monitor_contenders_; 成员。class MoniConditionVariable 定义在 mutex.h 中。
下面看 monitor_contenders_.Wait() 的实现:
//art/runtime/base/mutex.cc void ConditionVariable::Wait(Thread* self) { /* guard_ 是 class ConditionVariable 的 Mutex& guard_; 类型的成员,这里只做一些检查 */ guard_.CheckSafeToWait(self); WaitHoldingLocks(self); }
WaitHoldingLocks() 的定义:
//art/runtime/base/mutex.cc void ConditionVariable::WaitHoldingLocks(Thread* self) { /* 检查self是否是当前线程 */ DCHECK(self == nullptr || self == Thread::Current()); #if ART_USE_FUTEXES num_waiters_++; /* 进入休眠等待前先释放自己的guard锁 */ guard_.ExclusiveUnlock(self); /* 成功返回0,失败返回-1。这里是主要休眠位置 */ if (futex(sequence_.Address(), FUTEX_WAIT_PRIVATE, cur_sequence, nullptr, nullptr, 0) != 0) { if ((errno != EINTR) && (errno != EAGAIN)) { PLOG(FATAL) << "futex wait failed for " << name_; } } /* 休眠回来又立即持有自己的guard锁 */ guard_.ExclusiveLock(self); num_waiters_--; #else ... #endif }
5.6 monitor-exit 流程分析 —— Android-S上实现:
解释执行和机器码执行模式都会调用到 Monitor::MonitorExit 函数。
//art/runtime/monitor.cc bool Monitor::MonitorExit(Thread* self, ObjPtr<mirror::Object> obj) { while (true) { LockWord lock_word = obj->GetLockWord(true); /* 获取 LockWord 状态 */ switch (lock_word.GetState()) { /* 当状态为 hash 或 unlocked 状态时,通过 FailedUnlock 函数抛出异常 */ case LockWord::kHashCode: // Fall-through. case LockWord::kUnlocked: FailedUnlock(h_obj.Get(), self->GetThreadId(), 0u, nullptr); return false; // Failure. /* * 当 LockWord 的状态为 thin lock 状态时,有下面两种情况: * 1) 锁的 owner 与当前线程不一致,则出错抛出异常。 * 2) 锁的 owner 与当前线程为同一线程,当锁有重入时,则将 lock count -1,否则设置为 unlocked 状态。 */ 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(); if (lock_word.ThinLockCount() != 0) { uint32_t new_count = lock_word.ThinLockCount() - 1; new_lw = LockWord::FromThinLockId(thread_id, new_count, lock_word.GCState()); } else { new_lw = LockWord::FromDefault(lock_word.GCState()); } continue; // Go again. } } /* 当 LockWord 的状态为 fat lock 状态时,获取该对象关联的 Monitor 对象,并调用 Unlock 函数 */ case LockWord::kFatLocked: { Monitor* mon = lock_word.FatLockMonitor(); return mon->Unlock(self); //主要路径 } default: { LOG(FATAL) << "Invalid monitor state " << lock_word.GetState(); UNREACHABLE(); } } } }
下面看 Monitor::Unlock()
//art/runtime/monitor.cc bool Monitor::Unlock(Thread* self) { DCHECK(self != nullptr); Thread* owner = owner_.load(std::memory_order_relaxed); if (owner == self) { // We own the monitor, so nobody else can be in here. CheckLockOwnerRequest(self); AtraceMonitorUnlock(); /* 在 Unlock 函数中若递归持锁深度 lock_count 为0,说明该线程不在持有该锁,唤醒阻塞在该锁上的线程 */ if (lock_count_ == 0) { owner_.store(nullptr, std::memory_order_relaxed); SignalWaiterAndReleaseMonitorLock(self); //主要路径 } else { --lock_count_; DCHECK(monitor_lock_.IsExclusiveHeld(self)); DCHECK_EQ(owner_.load(std::memory_order_relaxed), self); // Keep monitor_lock_, but pretend we released it. FakeUnlockMonitorLock(); } return true; } ... }
下面看 Monitor::SignalWaiterAndReleaseMonitorLock():
void Monitor::SignalWaiterAndReleaseMonitorLock(Thread* self) { while (wake_set_ != nullptr) { if (thread->GetWaitMonitor() != nullptr) { /* 调用到 Mutex 实现中 */ monitor_lock_.Unlock(self); //这个是Mutex类型的成员变量 Releases contenders. thread->GetWaitConditionVariable()->Signal(self); //获取的是 Thread::wait_cond_ 成员变量表示的条件变量,它是在Thread的构造方法中new赋值的,应该与本次分析无关 return; } }
monitor_lock_.Unlock(self); //A12上是这样的,A10这个位置的实现不一样
} //android/art/runtime/base/mutex.h constexpr int kWakeOne = 1; void Mutex::Unlock(Thread* self) { ExclusiveUnlock(self); }
下面看 Mutex::ExclusiveUnlock():
//art/runtime/base/mutex.cc void Mutex::ExclusiveUnlock(Thread* self) { recursion_count_--; if (!recursive_ || recursion_count_ == 0) { RegisterAsUnlocked(self); #if ART_USE_FUTEXES bool done = false; do { int32_t cur_state = state_and_contenders_.load(std::memory_order_relaxed); if (LIKELY((cur_state & kHeldMask) != 0)) { /* 我们不再是这个Mutex锁的owner了,这里将tid设置为0 */ exclusive_owner_.store(0 /* pid */, std::memory_order_relaxed); /* 将状态更改为不持锁状态,并施加适合锁释放的加载/存储顺序 */ uint32_t new_state = cur_state & ~kHeldMask; done = state_and_contenders_.CompareAndSetWeakRelease(cur_state, new_state); if (LIKELY(done)) { if (UNLIKELY(new_state != 0) /* have contenders */) { /* 每次只唤醒一个本线程的任务 */ futex(state_and_contenders_.Address(), FUTEX_WAKE_PRIVATE, kWakeOne, nullptr, nullptr, 0); } /* 我们只会在增加竞争者数量并确认锁仍然被持有后才会执行 futex wait。如果我们没有看到等待者,那么在执 * 行 CAS 时就不可能有任何 futex 在等待这个锁。之后新来的 futex 无法等待我们,因为 futex 等待调用会发 * 现锁可用并立即返回。 */ } } } while (!done); #else exclusive_owner_.store(0 /* pid */, std::memory_order_relaxed); CHECK_MUTEX_CALL(pthread_mutex_unlock, (&mutex_)); #endif } }
5.7 monitor-exit 流程分析 —— Android-Q上实现:
//art/runtime/monitor.cc bool Monitor::MonitorExit(Thread* self, ObjPtr<mirror::Object> obj) { DCHECK(self != nullptr); DCHECK(obj != nullptr); self->AssertThreadSuspensionIsAllowable(); obj = FakeUnlock(obj); StackHandleScope<1> hs(self); Handle<mirror::Object> h_obj(hs.NewHandle(obj)); while (true) { LockWord lock_word = obj->GetLockWord(true); /* 获取LockWord的状态 */ switch (lock_word.GetState()) { /* 当状态为 hash 或 unlocked 状态时,通过 FailedUnlock 函数抛出异常. */ case LockWord::kHashCode: // Fall-through. case LockWord::kUnlocked: FailedUnlock(h_obj.Get(), self->GetThreadId(), 0u, nullptr); return false; // Failure. /* * 当 LockWord 的状态为 thin lock 状态时,有下面两种情况: * 1). 锁的 owner 与当前线程不一致,则出错抛出异常。 * 2). 锁的 owner 与当前线程为同一线程,当锁有重入时,则将 lock_count-1,否则设置为 unlocked 状态。 */ 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 { /* 下面就是锁的owner是自己的情况了 */ // We own the lock, decrease the recursion count. LockWord new_lw = LockWord::Default(); if (lock_word.ThinLockCount() != 0) { uint32_t new_count = lock_word.ThinLockCount() - 1; new_lw = LockWord::FromThinLockId(thread_id, new_count, lock_word.GCState()); } else { new_lw = LockWord::FromDefault(lock_word.GCState()); } ... continue; // Go again. } } /* 当 LockWord 的状态为fat lock状态时,获取该对象关联的 Monitor 对象,并调用 Unlock 函数 */ case LockWord::kFatLocked: { Monitor* mon = lock_word.FatLockMonitor(); return mon->Unlock(self); //主要释放锁路径 } default: { LOG(FATAL) << "Invalid monitor state " << lock_word.GetState(); UNREACHABLE(); } } } }
下面看 Monitor::Unlock():
//art/runtime/monitor.cc bool Monitor::Unlock(Thread* self) { uint32_t owner_thread_id = 0u; monitor_lock_.Lock(self); Thread* owner = owner_; if (owner != nullptr) { owner_thread_id = owner->GetThreadId(); } /* * 在 Unlock 函数中 lock_count 为0,说明该线程不再持有该锁,通过 * SignalContendersAndReleaseMonitorLock 唤醒阻塞在该锁上的线程。 */ if (owner == self) { // We own the monitor, so nobody else can be in here. AtraceMonitorUnlock(); if (lock_count_ == 0) { //嵌套深度降为0了,就该唤醒等待的线程了 /* 条件变量中将owner清0是写在这里还是写在哪里,,应该是写在条件变量里面好些,这里有owner不一定阻塞啊 */ /* Unlock时将owner_设置为nullptr */ owner_ = nullptr; locking_method_ = nullptr; locking_dex_pc_ = 0; /* TODO: 这个函数中是如何调用到futex wake的? */ SignalContendersAndReleaseMonitorLock(self); return true; } else { --lock_count_; monitor_lock_.Unlock(self); return true; } } /* 我们不拥有它,所以我们无法解锁它。JNI规范规定,在这种情况下我们应该抛出 IllegalMonitorStateException */ FailedUnlock(GetObject(), self->GetThreadId(), owner_thread_id, this); monitor_lock_.Unlock(self); return false; }
下面看 SignalContendersAndReleaseMonitorLock() 的实现:
void Monitor::SignalContendersAndReleaseMonitorLock(Thread* self) { //monitor.cc /* 我们想要通知一个线程唤醒,以获取我们正在释放的监视器。这可能是一个 * 等待其自己的 ConditionVariable 的线程,也可能是一个等待 monitor_contenders_ 的线程。*/ while (wake_set_ != nullptr) { /* 翻译: 在这里没有被吵醒的风险;由于在我们准备好返回之前,monitor_lock_ 不会被释放,因 * 此在该方法检查完 wake_set_ 之前,notify 无法将当前线程从 wait_set_ 移动到 wake_set_。 */ Thread* thread = wake_set_; wake_set_ = thread->GetWaitNext(); thread->SetWaitNext(nullptr); /* 检查线程是否仍在等待 */ { /* * 在 wait() 的情况下,我们将获取另一个线程的 GetWaitMutex,同时保持自己的 GetWaitMutex。 * 这不会有死锁的风险,因为我们仅为 wake_set_ 中的线程获取此锁。线程只能从 Notify 或 NotifyAll 进入 * wake_set_,并且它们持有 monitor_lock_。 因此,我们在这里获取等待互斥锁的线程必须已经从 wait() * 中释放,因为直到我们选择要唤醒的线程之后我们才释放 monitor_lock_, 因此不存在以下锁顺序导致死锁的风险 : * 线程1等待 * 线程2等待 * 线程 3 将线程 1 和 2 从 wait_set_ 移至 wake_set_ * 线程1进入该块,并尝试获取线程2的 GetWaitMutex 来唤醒它 * 线程2进入该块,并尝试获取线程1的 GetWaitMutex 来唤醒它 * * 由于在获取待唤醒线程的 GetWaitMutex 之前,monitor_lock_ 不会释放,因此两个线程无法在持有自己的 * GetWaitMutex 的同时尝试获取对方的 GetWaitMutex,从而导致死锁。 */ MutexLock wait_mu(self, *thread->GetWaitMutex()); if (thread->GetWaitMonitor() != nullptr) { /* 释放锁,以便潜在唤醒的线程不会立即争用它。这里的锁顺序是:monitor_lock_, self->GetWaitMutex, * thread->GetWaitMutex */ monitor_lock_.Unlock(self); thread->GetWaitConditionVariable()->Signal(self); return; } } } /* 如果我们没有唤醒任何最初等待我们的线程,则唤醒一个竞争者。*/翻译: monitor_contenders_.Signal(self); //这个是A10特有的,A12不是这样的实现(A12上都没有 monitor_contenders_ 成员) monitor_lock_.Unlock(self); }
monitor_contenders_ 是 Monitor 类的 ConditionVariable 类型的成员变量,下面看其 Signal() 的实现:
void ConditionVariable::Signal(Thread* self) { //mutex.cc DCHECK(self == nullptr || self == Thread::Current()); //说明参数self可以传nullptr guard_.AssertExclusiveHeld(self); RequeueWaiters(1); } /* 上面固定传参count=1 */ void ConditionVariable::RequeueWaiters(int32_t count) { //mutex.cc /* 若是有waiter存在,则要执行"唤醒"动作 */ if (num_waiters_ > 0) { sequence_++; //表示有信号发生。 /* 将等待者从条件变量的 futex 移动到守卫(guard)的 futex 上,以便在互斥锁被释放时它们会被唤醒。####*/ bool done = futex(sequence_.Address(), FUTEX_REQUEUE_PRIVATE, //注意,使用的是REQUEUE #### /* Threads to wake */ 0, /* Threads to requeue*/ reinterpret_cast<const timespec*>(count), //只唤醒一个 guard_.state_and_contenders_.Address(), 0) != -1; if (!done && errno != EAGAIN && errno != EINTR) { PLOG(FATAL) << "futex requeue failed for " << name_; } } }
六、总结
1. A10和A12实现上有差异,对于 Synchronized 锁来说,A12是基于 Mutex 实现的,而A10是基于条件变量实现的(但是有使用Mutex进行保护),Monitor类中引入了 monitor_contenders_ 条件变量成员,而A12没有这个成员。
posted on 2025-05-04 15:50 Hello-World3 阅读(73) 评论(0) 收藏 举报