Android synchronized実現原理
50118 ワード
synchronizedキーワードは2つの場所で使用できます:1.同期コードブロックは、任意のobjectをロックしてもよいし、クラスであってもよい.2.通常の同期方法はクラスのインスタンスオブジェクトをロックし、静的同期方法はこのクラスをロックします.Androidでは、それらの実現原理はmonitorによって実現されています.大体の手順は、monitor-enter(ロック)->同期コードブロックまたは同期方法の実行->monitor-exit(ロック解除)です.例を挙げる.
/frameworks/base/services/core/java/com/android/server/UiThread.java
上の同期コードブロックに対応するDavilkバイトコード命令は以下の通りである.synchronizedのコードがmonitor-enterとmonitor-exitの間に埋め込まれていることがわかります.
Android実行時の初期化時にモニタプールが作成されます.
/platform/android/art/runtime/runtime.cc
/android/art/runtime/monitor_pool.h
ジルコニウムモニタプールは、1つのchunkを使用して1つのモニタに対応します.num_chunks_ 現在のchunkの数、capacity_が記録されています.現在のchunkの容量が記録されていますfirst_free_現在最初に使用可能なchunkアドレスが記録されています.モニタプールを初期化すると、AllocateChunkを呼び出してchunkを割り当て、その後、新しいモニタを使用する必要があるたびにAllocateChunkを呼び出してchunkを割り当てます.
/art/runtime/monitor_pool.cc
ART対objectの実装ではuint 32_tタイプのメンバーmonitor_,このobjetcを記載するためのLockWord.LockWordの用途Googleもlock_word.hに明記しました. thinlockに対して,LockWordヘッダ2ビットは00,その後14ビットはロック回数,最後にホームスレッドidである.fatlockの場合、LockWordの最初の2桁は01で、残りは対応するmonitorのidです.LockWordが0の場合、そのobjectがロックされていないことを示します.これはobjectdごとのモニタが初期化された状態です. thinlockのロックプロセス:MonitorEnterに入ると、objectをロックすることを説明します.ロックワードは初期化時に0であるため、スレッドid番号とロック回数(0、初回ロックを示す)でロックワードを生成し、CAS(Compare And Set)でロックワードを新たに生成するロックワードに設定する.このプロセスはthinlockのロックプロセスです. thinlockのアクセスプロセス:このobjectにthinlockのホームスレッドがアクセスされている場合、ロック回数を1加算した後、LockWordを更新します.ロック回数に制限があり、2の14回に達すると(この場合はほとんどないでしょう)、呼び出しInflateThinLockedはロック膨張によってthinlockをfatlockにアップグレードします.objectにアクセスしているスレッドが他のスレッドである場合、sched_が呼び出されます.yieldはプロセッサを放棄し,CPUに適切な他のスレッドを選択して実行させる.contention_countは、このスレッドがobjectにアクセスしようとしたが成功しなかった回数を記録したが、contention_countがしきい値を超えると、InflateThinLockedがロック膨張によってthinlockをfatlockにアップグレードするように呼び出されます.このしきい値はデフォルトで50ですが、"-XX:MaxSpinsBeforeThinLockInflation="で指定することもできます.ジルコニウムはthinlockがスピンロックであることがわかる.ロックの解放を待つ間、スレッドはスリープせず、プロセッサを一時的に譲り、continueでループを再実行し、LockWordに対応する状態がkUnlocked(ロック解除)であるかどうかを確認します.ロックが短時間で占有される場合、スピンロックが好ましい.しかしcontention_countがある程度を超えると、ロックが長時間占有され、スピンロックを使用すると追加のオーバーヘッド(CAS操作と待機)が発生し、thinlockがfatlockにアップグレードされることを示します. thinlockとは異なり、非所有者スレッドはfatlockがロックしたコードブロックにアクセスする際に条件変数monitor_を通過するcontenders_ 同期を実現します.fatlockはヘビー級ロックであり、ロックを持たないスレッドはロックが解放されて起動するまでブロックされます.正確にはthinlockはmonitorを用いず、monitorを用いたのはfatlockである.
/art/runtime/lock_word.h
/art/runtime/monitor.cc
ロックの膨張:現在のスレッドがロックを持つスレッドである場合、ロック膨張操作を直接実行します.現在のスレッドがロックを持つスレッドでない場合は、ロックを持つスレッドをブロックしてからロック膨張操作を行います.
/art/runtime/monitor.cc
ロックの膨張:MonitorPool::CreateMonitorは新しいmonitorを作成します.次のMonitor::Installは、CASによってロックされたobjectのLockWordをfatlock対応のLockWord、すなわちヘッドマーク「01」と作成したばかりのmonitor idを組み合わせたLockWordに書き換えます.これにより、このobjectのロックを読み込むとfatlockであることがわかり、Monitor::Lockの流れに入る.
/art/runtime/monitor.cc
Monitor::MonitorExit:thinlockの場合、LockWordに記録されているロック回数が0でなければ、LockWordに記録されているロック回数を1減少させる.ロックワードに記録されているロック回数が0であれば、ロックワードを0クリアし、このobjectのロックを後でスレッドが取得すると、このロックはkUnlocked状態であり、このロックを直接占有することができる.fatlockの場合、条件変数monitor_によってcontenders_ のsignal関数は、このロックにブロックされているスレッドを呼び覚ます.
/art/runtime/monitor.cc
通常、ロック膨張の動作は一方向であり、すなわちthinlockはfatlockに膨張することができるが、fatlockはthinlockに収縮することができない.しかし、バックグラウンドプロセスでスタックカットを行うと、すべてのfatlockがthinlockに収縮します.また、Objectをよく使います....notify()は、スレッドの同期操作を行う.この2つのメソッドは、同じObjectをロック対象とするsynchronized文ブロックに使用する必要があります.いずれもnativeメソッドです.原因は以下のコード分析を見てください.
/art/runtime/native/java_lang_Object.cc
/art/runtime/mirror/object-inl.h
wait()の内部実装ではobjectのMonitorのLockWordがチェックされていることがわかる.ロック状態でないと、異常が直接放出する「object not locked by thread before wait()」は、wait()関数を使用する前にsynchronizedを使用してロックする必要があることを示しています.これも私たちが見たObjectです.wait()はsynchronized文の内部で使用される理由です.ロックワードがロックがThinLockであることを示す場合、ロックが属するスレッドが現在のスレッドでない場合、例外も放出されます」object not locked by thread before wait().ロックが属するスレッドが現在のスレッドである場合、ThinLockロックはFatLockに膨張します.膨張プロセスはCASを使用する必要があるため、「fail spuriously」になる可能性があり、whileサイクルを再実行して再びロック膨張を行う.ロック膨張に成功すると、Monitorのリロードバージョンのwait関数が呼び出されます.
/art/runtime/monitor.cc
はObjectを呼び出すためである.wait()関数の場合は時間パラメータが指定されていないため、パラメータms,nsはいずれも0である.
/art/runtime/monitor.cc
前述したように、Monitor::WaitではFatlockロックが解放され、競合スレッドがロックを取得して実行されます.wait_set_Objectのためにスレッドを取り出しますwait()(または他のwaitリロードバージョン)がブロックされている場合は、起動します.だから、Object.notify()は,閉塞状態に入る前後順に目覚ましの前後順を決定し,誰が先に閉塞するかは,先に目覚ましされる.しかし、Object.notify()は、他の起動したスレッドがロックを取り戻すことができることを示していません.notifyが存在するsynchronized文ブロックで実行するには、起動したスレッドがロックを再ロックする必要があります.No再びMonitor::Lockにブロックされます.私が見た限りでは、notify呼び出しはsynchronized文ブロックの最後の文です.
/art/runtime/monitor.cc
/frameworks/base/services/core/java/com/android/server/UiThread.java
public static Handler getHandler() {
synchronized (UiThread.class) {
ensureThreadLocked();
return sHandler;
}
}
上の同期コードブロックに対応するDavilkバイトコード命令は以下の通りである.synchronizedのコードがmonitor-enterとmonitor-exitの間に埋め込まれていることがわかります.
3: android.os.Handler com.android.server.UiThread.getHandler() (dex_method_idx=6892)
DEX CODE:
0x0000: const-class v1, com.android.server.UiThread // type@1398
0x0002: monitor-enter v1
0x0003: invoke-static {}, void com.android.server.UiThread.ensureThreadLocked() // method@6890
0x0006: sget-object v0, Landroid/os/Handler; com.android.server.UiThread.sHandler // field@2629
0x0008: monitor-exit v1
0x0009: return-object v0
0x000a: move-exception v0
0x000b: monitor-exit v1
0x000c: throw v0
Android実行時の初期化時にモニタプールが作成されます.
/platform/android/art/runtime/runtime.cc
monitor_pool_ = MonitorPool::Create();
/android/art/runtime/monitor_pool.h
static MonitorPool* Create() {
#ifndef __LP64__
return nullptr;
#else
return new MonitorPool();
#endif
}
ジルコニウムモニタプールは、1つのchunkを使用して1つのモニタに対応します.num_chunks_ 現在のchunkの数、capacity_が記録されています.現在のchunkの容量が記録されていますfirst_free_現在最初に使用可能なchunkアドレスが記録されています.モニタプールを初期化すると、AllocateChunkを呼び出してchunkを割り当て、その後、新しいモニタを使用する必要があるたびにAllocateChunkを呼び出してchunkを割り当てます.
/art/runtime/monitor_pool.cc
MonitorPool::MonitorPool()
: num_chunks_(0), capacity_(0), first_free_(nullptr) {
AllocateChunk(); // Get our first chunk.
}
// Assumes locks are held appropriately when necessary.
// We do not need a lock in the constructor, but we need one when in CreateMonitorInPool.
void MonitorPool::AllocateChunk() {
DCHECK(first_free_ == nullptr);
// Do we need to resize?
if (num_chunks_ == capacity_) {
if (capacity_ == 0U) {
// Initialization.
capacity_ = kInitialChunkStorage;
uintptr_t* new_backing = new uintptr_t[capacity_];
monitor_chunks_.StoreRelaxed(new_backing);
} else {
size_t new_capacity = 2 * capacity_;
uintptr_t* new_backing = new uintptr_t[new_capacity];
uintptr_t* old_backing = monitor_chunks_.LoadRelaxed();
memcpy(new_backing, old_backing, sizeof(uintptr_t) * capacity_);
monitor_chunks_.StoreRelaxed(new_backing);
capacity_ = new_capacity;
old_chunk_arrays_.push_back(old_backing);
VLOG(monitor) << "Resizing to capacity " << capacity_;
}
}
// Allocate the chunk.
void* chunk = allocator_.allocate(kChunkSize);
// Check we allocated memory.
CHECK_NE(reinterpret_cast(nullptr), reinterpret_cast(chunk));
// Check it is aligned as we need it.
CHECK_EQ(0U, reinterpret_cast(chunk) % kMonitorAlignment);
// Add the chunk.
*(monitor_chunks_.LoadRelaxed() + num_chunks_) = reinterpret_cast(chunk);
num_chunks_++;
// Set up the free list
Monitor* last = reinterpret_cast(reinterpret_cast(chunk) +
(kChunkCapacity - 1) * kAlignedMonitorSize);
last->next_free_ = nullptr;
// Eagerly compute id.
last->monitor_id_ = OffsetToMonitorId((num_chunks_ - 1) * kChunkSize +
(kChunkCapacity - 1) * kAlignedMonitorSize);
for (size_t i = 0; i < kChunkCapacity - 1; ++i) {
Monitor* before = reinterpret_cast(reinterpret_cast(last) -
kAlignedMonitorSize);
before->next_free_ = last;
// Derive monitor_id from last.
before->monitor_id_ = OffsetToMonitorId(MonitorIdToOffset(last->monitor_id_) -
kAlignedMonitorSize);
last = before;
}
DCHECK(last == reinterpret_cast(chunk));
first_free_ = last;
}
ART対objectの実装ではuint 32_tタイプのメンバーmonitor_,このobjetcを記載するためのLockWord.LockWordの用途Googleもlock_word.hに明記しました. thinlockに対して,LockWordヘッダ2ビットは00,その後14ビットはロック回数,最後にホームスレッドidである.fatlockの場合、LockWordの最初の2桁は01で、残りは対応するmonitorのidです.LockWordが0の場合、そのobjectがロックされていないことを示します.これはobjectdごとのモニタが初期化された状態です. thinlockのロックプロセス:MonitorEnterに入ると、objectをロックすることを説明します.ロックワードは初期化時に0であるため、スレッドid番号とロック回数(0、初回ロックを示す)でロックワードを生成し、CAS(Compare And Set)でロックワードを新たに生成するロックワードに設定する.このプロセスはthinlockのロックプロセスです. thinlockのアクセスプロセス:このobjectにthinlockのホームスレッドがアクセスされている場合、ロック回数を1加算した後、LockWordを更新します.ロック回数に制限があり、2の14回に達すると(この場合はほとんどないでしょう)、呼び出しInflateThinLockedはロック膨張によってthinlockをfatlockにアップグレードします.objectにアクセスしているスレッドが他のスレッドである場合、sched_が呼び出されます.yieldはプロセッサを放棄し,CPUに適切な他のスレッドを選択して実行させる.contention_countは、このスレッドがobjectにアクセスしようとしたが成功しなかった回数を記録したが、contention_countがしきい値を超えると、InflateThinLockedがロック膨張によってthinlockをfatlockにアップグレードするように呼び出されます.このしきい値はデフォルトで50ですが、"-XX:MaxSpinsBeforeThinLockInflation="で指定することもできます.ジルコニウムはthinlockがスピンロックであることがわかる.ロックの解放を待つ間、スレッドはスリープせず、プロセッサを一時的に譲り、continueでループを再実行し、LockWordに対応する状態がkUnlocked(ロック解除)であるかどうかを確認します.ロックが短時間で占有される場合、スピンロックが好ましい.しかしcontention_countがある程度を超えると、ロックが長時間占有され、スピンロックを使用すると追加のオーバーヘッド(CAS操作と待機)が発生し、thinlockがfatlockにアップグレードされることを示します. thinlockとは異なり、非所有者スレッドはfatlockがロックしたコードブロックにアクセスする際に条件変数monitor_を通過するcontenders_ 同期を実現します.fatlockはヘビー級ロックであり、ロックを持たないスレッドはロックが解放されて起動するまでブロックされます.正確にはthinlockはmonitorを用いず、monitorを用いたのはfatlockである.
/art/runtime/lock_word.h
/* The lock value itself as stored in mirror::Object::monitor_. The two most significant bits of
* the state. The three possible states are fat locked, thin/unlocked, and hash code.
* When the lock word is in the "thin" state and its bits are formatted as follows:
*
* |33|22222222221111|1111110000000000|
* |10|98765432109876|5432109876543210|
* |00| lock count |thread id owner |
*
* When the lock word is in the "fat" state and its bits are formatted as follows:
*
* |33|222222222211111111110000000000|
* |10|987654321098765432109876543210|
* |01| MonitorId |
*
* When the lock word is in hash state and its bits are formatted as follows:
*
* |33|222222222211111111110000000000|
* |10|987654321098765432109876543210|
* |10| HashCode |
*/
/art/runtime/monitor.cc
mirror::Object* Monitor::MonitorEnter(Thread* self, mirror::Object* obj) {
DCHECK(self != NULL);
DCHECK(obj != NULL);
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(true);
switch (lock_word.GetState()) {
case LockWord::kUnlocked: {
LockWord thin_locked(LockWord::FromThinLockId(thread_id, 0));
if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, thin_locked)) {
// CasLockWord enforces more than the acquire ordering we need here.
return h_obj.Get(); // Success!
}
continue; // Go again.
}
case LockWord::kThinLocked: {
uint32_t owner_thread_id = lock_word.ThinLockOwner();
if (owner_thread_id == thread_id) {
// We own the lock, increase the recursion count.
uint32_t new_count = lock_word.ThinLockCount() + 1;
if (LIKELY(new_count <= LockWord::kThinLockMaxCount)) {
LockWord thin_locked(LockWord::FromThinLockId(thread_id, new_count));
h_obj->SetLockWord(thin_locked, true);
return h_obj.Get(); // Success!
} else {
// We'd overflow the recursion count, so inflate the monitor.
InflateThinLocked(self, h_obj, lock_word, 0);
}
} else {
// Contention.
contention_count++;
Runtime* runtime = Runtime::Current();
if (contention_count <= runtime->GetMaxSpinsBeforeThinkLockInflation()) {
// TODO: Consider switching the thread state to kBlocked when we are yielding.
// Use sched_yield instead of NanoSleep since NanoSleep can wait much longer than the
// parameter you pass in. This can cause thread suspension to take excessively long
// and make long pauses. See b/16307460.
sched_yield();
} else {
contention_count = 0;
InflateThinLocked(self, h_obj, lock_word, 0);
}
}
continue; // Start from the beginning.
}
case LockWord::kFatLocked: {
Monitor* mon = lock_word.FatLockMonitor();
mon->Lock(self);
return h_obj.Get(); // Success!
}
case LockWord::kHashCode:
// Inflate with the existing hashcode.
Inflate(self, nullptr, h_obj.Get(), lock_word.GetHashCode());
continue; // Start from the beginning.
default: {
LOG(FATAL) << "Invalid monitor state " << lock_word.GetState();
return h_obj.Get();
}
}
}
}
ロックの膨張:現在のスレッドがロックを持つスレッドである場合、ロック膨張操作を直接実行します.現在のスレッドがロックを持つスレッドでない場合は、ロックを持つスレッドをブロックしてからロック膨張操作を行います.
/art/runtime/monitor.cc
void Monitor::InflateThinLocked(Thread* self, Handle<mirror::Object> obj, LockWord lock_word,
uint32_t hash_code) {
DCHECK_EQ(lock_word.GetState(), LockWord::kThinLocked);
uint32_t owner_thread_id = lock_word.ThinLockOwner();
if (owner_thread_id == self->GetThreadId()) {
// We own the monitor, we can easily inflate it.
Inflate(self, self, obj.Get(), hash_code);
} else {
ThreadList* thread_list = Runtime::Current()->GetThreadList();
// Suspend the owner, inflate. First change to blocked and give up mutator_lock_.
self->SetMonitorEnterObject(obj.Get());
bool timed_out;
Thread* owner;
{
ScopedThreadStateChange tsc(self, kBlocked);
// Take suspend thread lock to avoid races with threads trying to suspend this one.
MutexLock mu(self, *Locks::thread_list_suspend_thread_lock_);
owner = thread_list->SuspendThreadByThreadId(owner_thread_id, false, &timed_out);
}
if (owner != nullptr) {
// We succeeded in suspending the thread, check the lock's status didn't change.
lock_word = obj->GetLockWord(true);
if (lock_word.GetState() == LockWord::kThinLocked &&
lock_word.ThinLockOwner() == owner_thread_id) {
// Go ahead and inflate the lock.
Inflate(self, owner, obj.Get(), hash_code);
}
thread_list->Resume(owner, false);
}
self->SetMonitorEnterObject(nullptr);
}
}
ロックの膨張:MonitorPool::CreateMonitorは新しいmonitorを作成します.次のMonitor::Installは、CASによってロックされたobjectのLockWordをfatlock対応のLockWord、すなわちヘッドマーク「01」と作成したばかりのmonitor idを組み合わせたLockWordに書き換えます.これにより、このobjectのロックを読み込むとfatlockであることがわかり、Monitor::Lockの流れに入る.
/art/runtime/monitor.cc
void Monitor::Inflate(Thread* self, Thread* owner, 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);
}
}
Monitor::MonitorExit:thinlockの場合、LockWordに記録されているロック回数が0でなければ、LockWordに記録されているロック回数を1減少させる.ロックワードに記録されているロック回数が0であれば、ロックワードを0クリアし、このobjectのロックを後でスレッドが取得すると、このロックはkUnlocked状態であり、このロックを直接占有することができる.fatlockの場合、条件変数monitor_によってcontenders_ のsignal関数は、このロックにブロックされているスレッドを呼び覚ます.
/art/runtime/monitor.cc
bool Monitor::MonitorExit(Thread* self, mirror::Object* obj) {
DCHECK(self != NULL);
DCHECK(obj != NULL);
obj = FakeUnlock(obj);
LockWord lock_word = obj->GetLockWord(true);
StackHandleScope<1> hs(self);
Handle<mirror::Object> h_obj(hs.NewHandle(obj));
switch (lock_word.GetState()) {
case LockWord::kHashCode:
// Fall-through.
case LockWord::kUnlocked:
FailedUnlock(h_obj.Get(), self, nullptr, 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) {
// TODO: there's a race here with the owner dying while we unlock.
Thread* owner =
Runtime::Current()->GetThreadList()->FindThreadByThreadId(lock_word.ThinLockOwner());
FailedUnlock(h_obj.Get(), self, owner, nullptr);
return false; // Failure.
} else {
// We own the lock, decrease the recursion count.
if (lock_word.ThinLockCount() != 0) {
uint32_t new_count = lock_word.ThinLockCount() - 1;
LockWord thin_locked(LockWord::FromThinLockId(thread_id, new_count));
h_obj->SetLockWord(thin_locked, true);
} else {
h_obj->SetLockWord(LockWord(), true);
}
return true; // Success!
}
}
case LockWord::kFatLocked: {
Monitor* mon = lock_word.FatLockMonitor();
return mon->Unlock(self);
}
default: {
LOG(FATAL) << "Invalid monitor state " << lock_word.GetState();
return false;
}
}
}
通常、ロック膨張の動作は一方向であり、すなわちthinlockはfatlockに膨張することができるが、fatlockはthinlockに収縮することができない.しかし、バックグラウンドプロセスでスタックカットを行うと、すべてのfatlockがthinlockに収縮します.また、Objectをよく使います....notify()は、スレッドの同期操作を行う.この2つのメソッドは、同じObjectをロック対象とするsynchronized文ブロックに使用する必要があります.いずれもnativeメソッドです.原因は以下のコード分析を見てください.
/art/runtime/native/java_lang_Object.cc
static void Object_wait(JNIEnv* env, jobject java_this) {
ScopedFastNativeObjectAccess soa(env);
mirror::Object* o = soa.Decode<:object>(java_this);
o->Wait(soa.Self());
}
/art/runtime/mirror/object-inl.h
inline void Object::Wait(Thread* self) {
Monitor::Wait(self, this, 0, 0, true, kWaiting);
}
inline void Object::Wait(Thread* self, int64_t ms, int32_t ns) {
Monitor::Wait(self, this, ms, ns, true, kTimedWaiting);
}
wait()の内部実装ではobjectのMonitorのLockWordがチェックされていることがわかる.ロック状態でないと、異常が直接放出する「object not locked by thread before wait()」は、wait()関数を使用する前にsynchronizedを使用してロックする必要があることを示しています.これも私たちが見たObjectです.wait()はsynchronized文の内部で使用される理由です.ロックワードがロックがThinLockであることを示す場合、ロックが属するスレッドが現在のスレッドでない場合、例外も放出されます」object not locked by thread before wait().ロックが属するスレッドが現在のスレッドである場合、ThinLockロックはFatLockに膨張します.膨張プロセスはCASを使用する必要があるため、「fail spuriously」になる可能性があり、whileサイクルを再実行して再びロック膨張を行う.ロック膨張に成功すると、Monitorのリロードバージョンのwait関数が呼び出されます.
/art/runtime/monitor.cc
/*
* Object.wait(). Also called for class init.
*/
void Monitor::Wait(Thread* self, mirror::Object *obj, int64_t ms, int32_t ns,
bool interruptShouldThrow, ThreadState why) {
DCHECK(self != nullptr);
DCHECK(obj != nullptr);
LockWord lock_word = obj->GetLockWord(true);
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();
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 {
// We own the lock, inflate to enqueue ourself on the Monitor. May fail spuriously so
// re-load.
Inflate(self, self, obj, 0);
lock_word = obj->GetLockWord(true);
}
break;
}
case LockWord::kFatLocked: // Unreachable given the loop condition above. Fall-through.
default: {
LOG(FATAL) << "Invalid monitor state " << lock_word.GetState();
return;
}
}
}
Monitor* mon = lock_word.FatLockMonitor();
mon->Wait(self, ms, ns, interruptShouldThrow, why);
}
はObjectを呼び出すためである.wait()関数の場合は時間パラメータが指定されていないため、パラメータms,nsはいずれも0である.
/art/runtime/monitor.cc
void Monitor::Wait(Thread* self, int64_t ms, int32_t ns,
bool interruptShouldThrow, ThreadState why) {
DCHECK(self != NULL);
DCHECK(why == kTimedWaiting || why == kWaiting || why == kSleeping);
//monitor_lock_ , Lock Unlock
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;
}
// We need to turn a zero-length timed wait into a regular wait because
// Object.wait(0, 0) is defined as Object.wait(0), which is defined as Object.wait().
// kWaiting
if (why == kTimedWaiting && (ms == 0 && ns == 0)) {
why = kWaiting;
}
// Enforce the timeout range.
if (ms < 0 || ns < 0 || ns > 999999) {
monitor_lock_.Unlock(self);
ThrowLocation throw_location = self->GetCurrentLocationForThrow();
self->ThrowNewExceptionF(throw_location, "Ljava/lang/IllegalArgumentException;",
"timeout arguments out of range: ms=%" PRId64 " ns=%d", ms, ns);
return;
}
/*
* Add ourselves to the set of threads waiting on this monitor, and
* release our hold. We need to let it go even if we're a few levels
* deep in a recursive lock, and we need to restore that later.
*
* We append to the wait set ahead of clearing the count and owner
* fields so the subroutine can check that the calling thread owns
* the monitor. Aside from that, the order of member updates is
* not order sensitive as we hold the pthread mutex.
*/
// wait_set_
AppendToWaitSet(self);
// 1,
++num_waiters_;
// ,
int prev_lock_count = lock_count_;
lock_count_ = 0;
owner_ = NULL;
mirror::ArtMethod* saved_method = locking_method_;
locking_method_ = NULL;
uintptr_t saved_dex_pc = locking_dex_pc_;
locking_dex_pc_ = 0;
/*
* Update thread state. If the GC wakes up, it'll ignore us, knowing
* that we won't touch any references in this state, and we'll check
* our suspend mode before we transition out.
*/
//
self->TransitionFromRunnableToSuspended(why);
bool was_interrupted = false;
{
// Pseudo-atomically wait on self's wait_cond_ and release the monitor lock.
MutexLock mu(self, *self->GetWaitMutex());
// Set wait_monitor_ to the monitor object we will be waiting on. When wait_monitor_ is
// non-NULL a notifying or interrupting thread must signal the thread's wait_cond_ to wake it
// up.
DCHECK(self->GetWaitMonitor() == nullptr);
// wait_monitor_ monitor, monitor
self->SetWaitMonitor(this);
// Release the monitor lock.
// monitor_contenders_ , , FatLock , monitor_contenders_
monitor_contenders_.Signal(self);
monitor_lock_.Unlock(self);
// Handle the case where the thread was interrupted before we called wait().
if (self->IsInterruptedLocked()) {
was_interrupted = true;
} else {
// Wait for a notification or a timeout to occur.
if (why == kWaiting) {
// ,
self->GetWaitConditionVariable()->Wait(self);
} else {
DCHECK(why == kTimedWaiting || why == kSleeping) << why;
self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
}
if (self->IsInterruptedLocked()) {
was_interrupted = true;
}
self->SetInterruptedLocked(false);
}
}
// , kSuspendRequest , ; Runnable ( )。
// Set self->status back to kRunnable, and self-suspend if needed.
self->TransitionFromSuspendedToRunnable();
{
// We reset the thread's wait_monitor_ field after transitioning back to runnable so
// that a thread in a waiting/sleeping state has a non-null wait_monitor_ for debugging
// and diagnostic purposes. (If you reset this earlier, stack dumps will claim that threads
// are waiting on "null".)
MutexLock mu(self, *self->GetWaitMutex());
DCHECK(self->GetWaitMonitor() != nullptr);
//// wait_monitor_
self->SetWaitMonitor(nullptr);
}
// Re-acquire the monitor and lock.
//
Lock(self);
monitor_lock_.Lock(self);
self->GetWaitMutex()->AssertNotHeld(self);
/*
* We remove our thread from wait set after restoring the count
* and owner fields so the subroutine can check that the calling
* thread owns the monitor. Aside from that, the order of member
* updates is not order sensitive as we hold the pthread mutex.
*/
// ,
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);
if (was_interrupted) {
/*
* We were interrupted while waiting, or somebody interrupted an
* un-interruptible thread earlier and we're bailing out immediately.
*
* The doc sayeth: "The interrupted status of the current thread is
* cleared when this exception is thrown."
*/
{
MutexLock mu(self, *self->GetWaitMutex());
self->SetInterruptedLocked(false);
}
if (interruptShouldThrow) {
ThrowLocation throw_location = self->GetCurrentLocationForThrow();
self->ThrowNewException(throw_location, "Ljava/lang/InterruptedException;", NULL);
}
}
}
前述したように、Monitor::WaitではFatlockロックが解放され、競合スレッドがロックを取得して実行されます.wait_set_Objectのためにスレッドを取り出しますwait()(または他のwaitリロードバージョン)がブロックされている場合は、起動します.だから、Object.notify()は,閉塞状態に入る前後順に目覚ましの前後順を決定し,誰が先に閉塞するかは,先に目覚ましされる.しかし、Object.notify()は、他の起動したスレッドがロックを取り戻すことができることを示していません.notifyが存在するsynchronized文ブロックで実行するには、起動したスレッドがロックを再ロックする必要があります.No再びMonitor::Lockにブロックされます.私が見た限りでは、notify呼び出しはsynchronized文ブロックの最後の文です.
/art/runtime/monitor.cc
void Monitor::Notify(Thread* self) {
DCHECK(self != NULL);
MutexLock mu(self, monitor_lock_);
// Make sure that we hold the lock.
if (owner_ != self) {
ThrowIllegalMonitorStateExceptionF("object not locked by thread before notify()");
return;
}
// Signal the first waiting thread in the wait set.
while (wait_set_ != NULL) {
Thread* thread = wait_set_;
wait_set_ = thread->GetWaitNext();
thread->SetWaitNext(nullptr);
// Check to see if the thread is still waiting.
MutexLock mu(self, *thread->GetWaitMutex());
if (thread->GetWaitMonitor() != nullptr) {
thread->GetWaitConditionVariable()->Signal(self);
return;
}
}
}