2015-08-13 53 views
1

根據我的理解,當我使用同步塊時,它獲取對象的鎖定並在代碼塊完成執行時釋放它。在下面的代碼中同步塊鎖定對象和等待/通知

public class WaitAndNotify extends Thread{ 

    long sum; 

    public static void main(String[] args) { 
     WaitAndNotify wan = new WaitAndNotify(); 
     //wan.start(); 
     synchronized(wan){ 
      try { 
       wan.wait(); 
      } catch (InterruptedException ex) { 
       Logger.getLogger(WaitAndNotify.class.getName()).log(Level.SEVERE, null, ex); 
      } 
      System.out.println("Sum is : " + wan.sum); 
     } 
    } 

    @Override 
    public void run(){ 
     synchronized(this){ 
      for(int i=0; i<1000000; i++){ 
       sum = sum + i; 
      } 
      notify(); 
     } 

    } 
} 

如果run方法中的同步塊首先獲取鎖,會發生什麼?然後,main方法中的synchronized塊必須等待(不是因爲wait(),因爲另一個線程獲取了鎖)。 run方法執行完成後,main方法是否會進入其synchronized塊並等待它永遠不會得到的通知?我在這裏誤解了什麼?

+0

從這些基本的API轉移到高級API,如java.util.concurrent.Executors和ExecutorService。查看代碼示例:http://examples.javacodegeeks.com/core-java/util/concurrent/executorservice/java-executorservice-example-tutorial/ –

+1

@ sunrise76,java.util.concurrent中的類不是「更高級的:「他們在更高級別的抽象層面上運作。編寫生產代碼的devloper絕對應該使用更高級別的工具,但是_student_可以很好地理解構建這些更高級別工具的原語。就像學過彙編語言的人在用更高級的語言編寫時做出更明智的決定一樣,所以瞭解互斥鎖,條件變量和原子操作的人可以更好地使用隊列和線程池等。 –

+0

您的理解是正確的。如果沒有其他線程在同一時間在'foo.wait()'調用中被阻塞,'foo.notify()'什麼也不做。 –

回答

1

是的,在wait()引起掛起的線程之前可能執行notify(),所以您需要小心以防止發生這種情況。

出於這個原因(和其他人),通常使用java.util.concurrent的更高層次結構更好,因爲它們通常會減少在腳中拍攝自己的可能性。

1

在這裏你不會看到'等待永遠'的問題,因爲你正在用超時的方式調用wait()的版本;因此,即使沒有收到通知,5秒後也會返回。 wait()調用的「等待永久」版本的確可能會出現您描述的問題。

+0

對不起,我忘了刪除第5部分。如果是wait(),notify()是否有可能在wait()之前運行? –

+0

是的。如果發生這種情況,主執行線程將掛起。 –

1

這裏有兩個線程:WaitAndNotify(WAN)線程和Java的主執行線程。兩人都在爭奪同一把鎖。

如果WAN線程首先獲取鎖定,主線程將被阻止。處於阻塞狀態不等於處於等待狀態。處於等待狀態的線程將在繼續前進之前等待通知。處於阻塞狀態的線程會在鎖定變爲可用時主動嘗試獲取鎖定(並繼續嘗試,直到鎖定爲止)。

假設run方法正常執行,它將調用notify(),這將不起作用,因爲沒有其他線程當前處於等待狀態。即使有,廣域網仍保持鎖定狀態,直到退出同步代碼塊。一旦WAN退出該塊,那麼Java將通知一個等待線程(如果有的話,沒有)。

此時,主執行線程現在獲得鎖(不再被阻塞)並進入等待狀態。現在,您已經使用等待的版本,將等待5000毫秒,然後繼續。如果您使用了vanilla版本(wait()),它將永遠等待,因爲沒有其他進程會通知它。

2

wait()隱含暫時退出相應的監視器,並重新進入它在返回:

wait()

當前線程必須擁有該對象的監視器。 線程發佈 此監視器的所有權並等待另一個線程通知 等待此對象監視器的線程通過調用notify方法或notifyAll方法的 喚醒。線程,然後 等待,直到它可以重新獲得顯示器的所有權並恢復執行 執行

這就是爲什麼以及這種同步如何起作用。

+0

[wait()](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--)釋放對象監視器時,[notify()](https ://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)/ [notifyAll的()](https://docs.oracle.com/javase/8/ docs/api/java/lang/Object.html#notifyAll--)不。 [「喚醒的線程將無法繼續,直到當前線程放棄對該對象的鎖定。」](https://docs.oracle.com/javase/8/docs/api/java/lang/Object。 HTML#notifyAll--) – naaz

1

下面是示例程序的一個版本,它改爲引入一個測試條件變量的循環。在從等待甦醒這樣你就避免對事物的狀態後一個線程壞的假設重新獲取一個鎖,而且也沒有兩個線程之間的順序關係:

public class W extends Thread { 
    long sum; 
    boolean done; 

    public static void main(String[] args) throws InterruptedException { 
     W w = new W(); 
     w.start(); 
     synchronized(w) { 
      while (!w.done) { 
       w.wait(); 
      } 
      // move to within synchronized block so sum 
      // updated value is required to be visible 
      System.out.println(w.sum); 
     } 
    } 

    @Override public synchronized void run() { 
     for (int i = 0; i < 1000000; i++) { 
      sum += i; 
     } 
     done = true; 
     // no notify required here, see nitpick at end 
    } 
} 

這是不夠的等待通知,因爲你指出的原因(順序依賴性,你依賴的是競爭條件,希望一個線程在另一個線程之前獲取監視器)以及其他原因。首先,一個線程可以在沒有收到通知的情況下從等待中喚醒,但是你不能認爲有任何通知。

當一個線程等待時,它需要在一個循環中完成,在循環的測試中,它檢查一些條件。另一個線程應該設置該條件變量,以便第一個線程可以檢查它。 the Oracle tutorial的建議是:

注意:始終在測試等待條件的循環中調用wait。不要認爲中斷是針對您正在等待的特定情況,或者情況依然如此。

其他吹毛求疵:

  • 當你的例子寫的,JVM不需要進行更改您的總和變量可見的主線程。如果添加一個同步實例方法來訪問sum變量,或者訪問同步塊內的和,那麼主線程將保證能夠看到sum的更新值。

  • 看着你的日誌記錄,沒有什麼關於InterruptedException的嚴重問題,它並不意味着任何錯誤。當您在線程上調用中斷,設置其中斷標誌,並且該線程當前正在等待或正在休眠,或者在標誌仍然置位時進入等待或休眠方法時,會導致InterruptedException。在我的答案頂部的玩具示例中,我將異常置於throws子句中,因爲我知道這不會發生。

  • 當線程終止時,它發出一個notifyAll,任何等待該對象的東西都會收到(同樣,這就是連接的實現方式)。部分原因是使用Runnable而不是Thread。

  • 在這個特殊的例子中,在求和線程上調用Thread#join會更有意義,而不是調用wait。

這裏的例子重新編寫,使用加入代替:

public class J extends Thread { 
    private long sum; 

    synchronized long getSum() {return sum;} 

    public static void main(String[] args) throws InterruptedException { 
     J j = new J(); 
     j.start(); 
     j.join(); 
     System.out.println(j.getSum()); 
    } 

    @Override public synchronized void run() { 
     for (int i = 0; i < 1000000; i++) { 
      sum += i; 
     }   
    } 
} 

線程#加入呼叫等待,鎖定線程對象。當求和線程終止時,它發送一個通知並將其isAlive標誌設置爲false。同時在join方法中,主線程正在等待求和線程對象,它接收到通知,檢查isAlive標誌,並且實現它不必再等待,因此它可以離開join方法並打印結果。