2009-12-17 47 views
3

我來自.NET世界,不幸的是看着Java的來源與.NET的眼睛。Java同步遊戲:同步&&等待&&通知

下面的代碼是從Android應用程序(雖然不是特定的Android在所有):

private class Worker implements Runnable { 
     private final Object mLock = new Object(); 
     private Looper mLooper; 

     Worker(String name) { 
      Thread t = new Thread(null, this, name); 
      t.start(); 
      synchronized (mLock) { 
       while (mLooper == null) { 
        try { 
         mLock.wait(); 
        } catch (InterruptedException ex) { 
        } 
       } 
      } 
     } 

     public Looper getLooper() { 
      return mLooper; 
     } 

     public void run() { 
      synchronized (mLock) { 
       Looper.prepare(); 
       mLooper = Looper.myLooper(); 
       mLock.notifyAll(); 
      } 
      Looper.loop(); 
     } 

     public void quit() { 
      mLooper.quit(); 
     } 
    } 

我不是如何​​作品精確清晰。 首先,我認爲同步鎖定mLock對象,但如果之後構造函數線程首先進入同步塊,它會阻止它在mLock.wait(),並通過阻止它進入同步塊隱式阻塞線程「t」。

這顯然是錯誤的,因爲我的手機響了像預想的那樣:)

下一個想法是,同步同步「代碼塊」(在這種情況下,有兩個同步塊是獨立=>線程可以輸入兩個不同的同步同時沒有任何限制地安裝),並且完全符合...

...直到我的同事告訴我mLock.wait()在mLock上釋放鎖並使其他線程在同一時間進入mLock的臨界區。

我不確定我是否足夠清楚,所以很樂意回答關於此問題的任何進一步問題。

+0

第一個問題:你的問題是什麼? – erickson 2009-12-17 14:56:23

+0

這段代碼如何工作? :) – Jox 2009-12-17 15:00:19

回答

8

查看Object.wait()上的javadoc。這是「魔術」,因爲它會丟棄進入同步塊時獲取的監視器。這允許另一個線程獲取監視器並呼叫Object.notify()

當另一個線程調用notify()從其wait()調用中喚醒等待的線程時,等待的線程必須重新獲取該監視器並將阻塞,直到它可以 - 監視器僅在等待()調用。通知線程在新喚醒的等待線程可以繼續之前完成其同步塊。一切都按照可預測的順序排列。

+1

這實際上是瞭解Java中同步的關鍵因素:'wait()'*發佈*鎖。一旦我完全明白這一點,一切都變得晶瑩剔透。 – Bombe 2009-12-17 15:05:00

+0

好的......現在更清楚:) 還有一個問題: 比方說,在run()的同步塊中,'mLock.notifyAll()'行後面還有幾行。 如果我正確地理解了,構造函數的線程不會在run()的notifyAll後立即繼續它的代碼執行,而只是在run()的線程離開同步塊之後才執行。 – Jox 2009-12-17 17:22:10

+2

正如notify()javadoc中所述,notify()調用方不會釋放監視器,直到它通常會(退出同步塊或其他)。因此,新喚醒的wait()線程將被阻塞,直到它可以重新獲取監視器。直到notify()調用者釋放它纔會發生。因此,在synchronized塊結束之前的notify()之後的代碼在任何其他線程獲得監視器之前肯定會運行。 – 2009-12-17 20:06:54

1

​​使用對象監視器。在對象上調用wait()以原子方式釋放對象監視器(否則其他線程無法拿到監視器並向服務器發出notify)。

1

是的。如果您閱讀wait()方法的描述,您將瞭解到它會導致線程釋放鎖並阻塞,直到另一個線程調用鎖上的notifynotifyAll。當前線程一直等待,直到它可以重新獲取該鎖,一旦它結束,它將繼續執行。

但是,由於它在完全構建之前「發佈」(也就是說,它使對象可以被其他線程訪問)實例,所以顯示的代碼很差。在這種方法中使用額外的障礙,加上班級的性質,可能會使這種情況變得安全,但通常情況並非如此。

0

讓我解釋一下:

構造函數啓動一個新的線程將執行run()方法。 這個新線程將獲得一個新的Looper對象,將其存儲在mLooper字段中,然後運行Looper消息循環。在它之間會通知()第一個線程,即已經設置了mLooper。

因此,只有在設置了mLooper之後,第一個線程纔會從構造函數返回,這意味着由第二個線程處理Looper.loop()將很快啓動/已經啓動。