【線程】Object.wait 內部原理(二)

我的原則:先會用再說,內部慢慢來


Object.wait

1. 作用

  1. wait 的作用是啥:讓出當前鎖住的對象,讓別的線程跑完再來弄我的線程。

2. 注意點

  1. 記住鎖是鎖對象,不是鎖住線程
  2. Join 方法是 Thread對象的,wait方法是 Object 對象的,Join的底層實現是 wait
  3. wait方法的使用必須在同步的範圍內,否則就會拋出 IllegalMonitorStateException 異常.
  4. wait方法的作用就是阻塞當前線程等待notify/notifyAll方法的喚醒,或等待超時後自動喚醒。
  5. 底層就是 ObjectMonitor 對象釋放了當前對象A,丟到 waitSet 區域去,然後重新鎖住了其他的對象。

3. 先看個例子

萬物皆對象,人在做天在看。對象在搶鎖,ObjectMonitor在看。

代碼:

public class _02_00_WaitTest {
	// 注意這裏的 synchronzied,待會去掉。
    public synchronized void testWait(){
        System.out.println(Thread.currentThread().getName() + ", Start-----");
        try {
            wait(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ", End-------");
    }

    public static void main(String[] args) throws Exception{
        final _02_00_WaitTest test = new _02_00_WaitTest();
        System.out.println("Main begin...");
        Thread.sleep(200); // 停頓的目的都是爲了保證打印的順序
        new Thread(() -> {
            test.testWait(); // thread調用wait方法,釋放所對象,讓給了Main線程先走
        },"childThreadDemo").start();
        Thread.sleep(200);
        System.out.println("Main end...");
    }
}

輸出:

Main begin...
childThreadDemo, Start-----
Main end...
childThreadDemo, End-------

這個結果很正常,子線程跑到一半,讓出鎖住的對象給Main線程,Main線程跑完,子線程再繼續跑。

4.假如去掉 synchronized

  1. 代碼:
public class _02_00_WaitTest {
	// 注意這裏的 synchronzied,待會去掉。
    public void testWait(){
        System.out.println(Thread.currentThread().getName() + ", Start-----");
        try {
            wait(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ", End-------");
    }
  1. 輸出:
Main begin...
childThreadDemo, Start-----
Exception in thread "childThreadDemo" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at indi.sword.util.basic.Thread._02_00_WaitTest.testWait(_02_00_WaitTest.java:25)
	at indi.sword.util.basic.Thread._02_00_WaitTest.lambda$main$0(_02_00_WaitTest.java:37)
	at java.lang.Thread.run(Thread.java:748)
Main end...

5. IllegalMonitorStateException 是啥玩意?

直接看源碼:

/**
 * Thrown to indicate that a thread has attempted to wait on an
 * object's monitor or to notify other threads waiting on an object's
 * monitor without owning the specified monitor.

 */
public
class IllegalMonitorStateException extends RuntimeException {
}

上面的註釋翻譯一下:

“監視器鎖”(ObjectMonitor)
線程試圖等待對象的監視器或者試圖通知其他正在等待對象監視器的線程,但本身沒有對應的監視器的所有權。 wait方法是一個本地方法,其底層是通過一個叫做"監視器鎖"(ObjectMonitor)的對象來完成的。所以上面之所以會拋出異常,是因爲在調用wait方式時沒有獲取到monitor對象的所有權.

6. ObjectMonitor 又是啥東東?

ObjectMonitor.hpp 源碼查看
ObjectMonitor.cpp 源碼查看

ObjectMonitor 是一個監視器,負責調度。
你可以這麼理解理解,他是屋子的主人,

  1. _owner:客廳招待客人,
  2. _EntryList:客人在門口還沒進來,
  3. _WaitSet:客人和主人剛聊完,現在去休息室休息一下

直接看 JVM 的源碼(C++寫的)

 ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;  // 這個指向哪個Object,JAVA層面來說,就是哪個Object就獲得了鎖
    _WaitSet      = NULL;  // 對象調用 wait後,就存在了這裏
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;  // 這是一個隊列,存儲準備獲取鎖的那堆在排隊的Object
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

在這裏插入圖片描述
看上面這麼一張圖,解釋上面的1234

1.enter: 對象進入 EntrySet (也就是在進入 synchronized 方法之前被blocking了),放在ObjectMonitor的 _EntrySet 隊列裏面
2. acquire:JAVA層面就是爭搶到鎖了,比如就是A對象搶到鎖了, 內部就是 ObjectMonitor的_owner 指針指向了A
3. release:java這邊調用了 wait方法,比如說A對象
4. acquire : A對象 wait(10) ,10ms到鍾了,或者是被 notify或者notifyAll 叫醒起來搶奪鎖了。然後搶到鎖了。
5. release and exit : 正常流程處理完畢,退出。

注意: _owner在一個時刻,只會指向一個對象,指到哪個Object就是哪個Object獲得到了鎖。所以你看上面黑色的原點在一個時刻只會有一個。

根據聯想記憶法:我們溫習一下線程的6種狀態:
1.初始(NEW),2. 正在運行(RUNNABLE) 3.等待(WAITING),4.超時等待(TIMED_WAITING),5.阻塞(BlOCKED) 6. 終止(TERMINATED)
在這裏插入圖片描述

7. ObjectWaiter 是啥玩意?

看一下 ObjectMonitor.cpp 源碼,裏面 wait 方法:

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
...
if (node.TState == ObjectWaiter::TS_WAIT) {
         Thread::SpinAcquire (&_WaitSetLock, "WaitSet - unlink") ;
         if (node.TState == ObjectWaiter::TS_WAIT) {
            DequeueSpecificWaiter (&node) ;       // unlink from WaitSet
            assert(node._notified == 0, "invariant");
            node.TState = ObjectWaiter::TS_RUN ;
         }
         Thread::SpinRelease (&_WaitSetLock) ;
     }
...
}

ObjectMonitor::DequeueSpecificWaiter 方法就是進入 _WaitSet 隊列。

也就是上面 _WaitSet 和 _EnterList 隊列裏面存的C++對象。


8. 番外篇

上一章節:【線程】 Thread.join 內部原理 (一)
下一章節:【線程】 Thread.sleep 與 Object.wait 的區別 (三)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章