天天看點

wait , notify, sleep 方法實作細節和差別比較

關于 wait 方法和 notify 方法實作細節主要是根據 官方的javaDoc 文檔和 openJDK 源碼中得出,jdk 源碼位址:

http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/30fb8c8cceb9/src/share/vm/runtime/objectMonitor.hpp

1. wait() 方法

1.1 調用wait 方法時,必須目前線程持有該對象的monitor

1.2 對象調用wait 方法後,它會将目前線程放入該對象的 等待集合(wait set)中,在JVM 的C++源碼中,本質是放入一個ObjectMonitor 中,裡面有一個屬性叫 _WaitSet ,就是用于存放ObjectWait 對象,

而ObjectWait 對象本質就是一個連結清單結構。ObjectMonitor 中持有該ObjectWait 的數組,源碼如下:

// ObjectWaiter serves as a "proxy" or surrogate thread.
// TODO-FIXME: Eliminate ObjectWaiter and use the thread-specific
// ParkEvent instead.  Beware, however, that the JVMTI code
// knows about ObjectWaiters, so we'll have to reconcile that code.
// See next_waiter(), first_waiter(), etc.

class ObjectWaiter : public StackObj {
 public:
  enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
  enum Sorted  { PREPEND, APPEND, SORTED } ;
  ObjectWaiter * volatile _next;
  ObjectWaiter * volatile _prev;
  Thread*       _thread;
  jlong         _notifier_tid;
  ParkEvent *   _event;
  volatile int  _notified ;
  volatile TStates TState ;
  Sorted        _Sorted ;           // List placement disposition
  bool          _active ;           // Contention monitoring is enabled
 public:
  ObjectWaiter(Thread* thread);

  void wait_reenter_begin(ObjectMonitor *mon);
  void wait_reenter_end(ObjectMonitor *mon);
};


// initialize the monitor, exception the semaphore, all other fields
  // are simple integers or pointers
  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }
           

1.3 隻有當對象調用了notify 或者 notifyAll 方法,又或者是等待時間逾時,該對象會到wait set 中根據不同的政策将線程從中移除,并将線程放入到 Enter list 中,與其它等待的線程共同競争該對象的鎖,一旦擷取到monitor ,該線程就會繼續往下執行。

1.4 如果目前線程被其它線程打斷,那麼它産生 InterruptedException ,當該對象重新擷取了鎖狀态,那麼該異常會抛出。

2. notify ,nofityAll 方法

2.1 調用notify 方法後,它會喚醒目前對象中正在waiting(等待)的多個線程中的一個線程。

2.2 被喚醒的線程并不會馬上進行執行它後面的程式,它需要在目前線程讓出monitor 後,被喚醒線程擷取對象的鎖後,才可以執行

2.3 被喚醒的線程并不會存在優先權或者不足,它與其它阻塞線程一樣,公平競争着對象的鎖。

2.4 持有對象的鎖有三種方式: synchronized 代碼塊中, 對象的synchronized 方法, 類中的靜态的synchronized 方法

2.5 一次隻能有一個線程持有對象的monitor

3. Thread.sleep() 方法

線程的sleep() 方法,它會将目前對象進行阻塞,并且會一直持有目前對象的鎖。

4. wait 與 sleep的差別:

1. 關于wait方法:

1.1 wait 是Object 類執行個體本地方法,它底層是由C++實作,

1.2 它隻能在目前線程獲得了對象的monitor 之後 才允許被調用 (也就是必須在synchronized 的方法中或者代碼塊中)。如果沒有在裡面進行調用,則會抛出 java.lang.IllegalMonitorStateException 異常。

注意:網上有文章說調用該方法不用捕獲異常,這是非常錯誤的結論,讀者在看時,最好自己動手确認一下。

1.3 通過将目前線程放入 等待集合中,該線程被挂起,同時釋放掉目前對象的monitor , 直到目前對象調用 nofity或者 notifyAll 方法後,才能被喚醒。

1.4被喚醒的的線程我會被移出wait set ,然後放入後到enter list 中,與其它等待線程一樣競争對象的monitor,如果拿到,則該線程則恢複原來操作繼續往下執行,否則将繼續等待。

PS:注意 wait 方法通常放入while 循環中進行,因為可能會出現假喚醒,而此時并不滿足條件 ,而讓代碼往下走。

2. 關于sleep 方法

2.1 它是Thread 類的靜态本地方法,它底層也是由C++實作,

2.2 它可以在任意地方被調用,需要異常的捕獲。

2.3 它會将目前線程進行休眠,同時并不會釋放與之相關的monitor.

2.4 它在休眠中會讓出CPU資源, 隻有休眠時間逾時後,線程恢複到可運作狀态。

2.5 當它被打斷時,會抛出 InterruptedException