2013-04-24 64 views
32

如何在不是線程的對象上調用wait()notify()方法?這真的沒有道理,是嗎?如何在不是線程的對象上調用wait()和notify()方法?

然而,它必須有意義,因爲這兩種方法都適用於所有Java對象。有人可以提供解釋嗎?我無法理解如何使用wait()notify()之間的線程進行通信。

+5

任何'Object'可以用作顯示器'Object',因此'Object'類'Object'實現了這些方法。 – Reimeus 2013-04-24 16:21:12

+1

我認爲他們在'Object'中的存在更像是一個「標記」,[Thread](http://docs.oracle.com/javase/7/docs/api/java/lang/Thread。html)extends Object – Coffee 2013-04-24 16:21:44

+7

'wait()','notify()'和'notifyAll()'只有在同步方法或同步塊內調用時纔有效。 – 2013-04-24 16:22:53

回答

27

鎖與線程不同。該鎖在受保護的數據結構上。線程是訪問數據結構的東西。鎖在數據結構對象上,以防止線程以不安全的方式訪問數據結構。

任何對象都可以用作固有鎖(意爲與​​結合使用)。這樣,您可以通過將同步修飾符添加到訪問共享數據的方法來防止訪問任何對象。 (這並不是一個好主意,因爲它允許任何可以訪問該對象的線程獲取其鎖,即使它沒有調用任何方法;最好將鎖作爲數據結構的私有成員鎖定,因此對其的訪問受到限制。)

waitnotify在用作鎖的對象上被調用。鎖是一個共享的通信地點:

  • 在有鎖的線程調用它notifyAll,等待對同一個鎖的其他線程得到通知。當一個有鎖的線程在其上調用notify時,等待該鎖的線程中的一個會被通知。當一個有鎖的線程調用wait時,線程釋放鎖並進入休眠狀態,直到:a)它收到通知,或者b)它只是隨意喚醒(「虛假喚醒」);等待的線程仍然停留在調用中,直到它由於以上兩個原因中的一個而被喚醒,那麼線程必須重新獲得該鎖,才能退出等待方法。

Oracle tutorial on guarded blocks,投遞類是共享的數據結構,使用生產者和消費者可運行的線程訪問它。鎖定Drop對象控制線程如何訪問Drop對象的數據。

線程被用作JVM實現中的鎖,建議應用程序開發人員避免使用線程作爲鎖。例如,documentation for Thread.join說:

此實現使用this.wait調用的條件this.isAlive的循環。當一個線程終止this.notifyAll方法被調用。建議應用程序不要在Thread實例上使用wait,notify或notifyAll。

+0

感謝您的解釋,我有一個問題,爲什麼設計就像等待,通知和notifyAll是可用於每個類的方法,因爲每個類都有父類作爲對象類,爲什麼它不喜歡有接口的方式是它可克隆我們需要重寫克隆方法的接口? – 2017-02-16 10:38:50

+0

@Rahul:不知道,但請記住,java最初是爲了關於小設備上的移動代碼而設計的。線程應該很容易,但他們並沒有考慮高度併發的服務器應用程序。 – 2017-02-17 04:16:19

+0

@NathanHughes「這不是一個好主意,因爲它允許任何可以訪問該對象的線程獲取其鎖,即使它沒有調用任何方法;最好將該鎖保留爲數據結構的私有成員被鎖定,所以訪問它是有限的。「 _請讓它更清晰._ – abksrv 2017-08-17 18:48:33

21

您可以使用wait()notify()來同步您的邏輯。作爲一個例子

synchronized (lock) { 
    lock.wait(); // Will block until lock.notify() is called on another thread. 
} 

// Somewhere else... 
... 
synchronized (lock) { 
    lock.notify(); // Will wake up lock.wait() 
} 

lock是類成員Object lock = new Object();

+4

一個簡單的使用這種事情是一個消息生產者/消費者其中 consumer.wait(); 直到 producer.notify(); – 2013-04-24 20:50:06

+2

我認爲這是最好的例子之一:http://www.javamex.com/tutorials/wait_notify_how_to.shtml – 2013-04-29 02:30:38

+0

這不是基本上鎖嗎? – shinzou 2018-02-18 15:53:11

3

Java中的所有對象實現這兩種方法,顯然如果沒有監控這兩種方法都沒有用。

1

「此方法只能由作爲此對象監視器所有者的線程調用。」 所以我認爲你必須確保有一個線程是對象上的監視器。

5

您可以根據需要使用靜態Thread類方法sleep()來停止線程。

public class Main { 
    //some code here 

    //Thre thread will sleep for 5sec. 
    Thread.sleep(5000); 
} 

如果你想停止某些對象需要內syncronized塊調用此方法的。

public class Main { 

//some code 

public void waitObject(Object object) throws InterruptedException { 
    synchronized(object) { 
     object.wait(); 
    } 
} 

public void notifyObject(Object object) throws InterruptedException { 
    synchronized(object) { 
     object.notify(); 
    } 
} 

}

附:我是索裏,如果我錯了明白你的問題(英語不是我的母語)

+1

謝謝,我喜歡這樣的解釋:http://www.javamex.com/tutorials/wait_notify_how_to.shtml – 2013-04-29 02:28:30

3

當你把synchronized塊裏面的一些代碼:

sychronized(lock){...} 

線程想執行任何裏面這個塊首先獲取鎖定一個對象,一次只能有一個線程執行鎖定在同一個對象上的代碼。任何對象都可以用作鎖,但您應該小心選擇與範圍相關的對象。例如,當你有多個線程添加一些東西到帳戶,它們都具有一定的代碼負責,像塊內:

sychronized(this){...} 

那麼沒有同步發生的,因爲他們都鎖定不同的對象。相反,你應該使用一個賬戶對象作爲鎖。 現在考慮這些線程也有退出帳戶的方法。在這種情況下,某個線程想要撤消某個事件時可能會遇到空賬戶。它應該等到有一些錢,並釋放鎖到其他線程,以避免死鎖。這就是等待和通知的方法。在這個例子中遇到空賬釋放鎖和一些線程,使存款等待信號線:

while(balance < amountToWithdraw){ 
    lock.wait(); 
} 

當其他線程存款些錢,它標誌着其他線程都在等待同一個鎖。 (當然,負責存款和取款的代碼必須在同一個鎖上進行同步操作,以防止數據損壞)。

balance += amountToDeposit; 
lock.signallAll; 

正如你所看到的方法等,並通知只會讓synchronized塊或方法中的意義。

2
  1. 等待和通知不僅僅是正常的方法或同步實用程序,更重要的是它們是Java中兩個線程之間的通信機制。如果這種機制不能通過像關鍵字這樣的任何java關鍵字獲得,那麼Object類是正確的地方,以使它們可用於每個對象。記住同步和等待通知是兩個不同的區域,不要混淆它們是相同的或相關的。同步是爲了提供互斥,確保Java類的線程安全性,例如競爭狀態,而wait和notify是兩個線程之間的通信機制。
  2. 鎖是可用的每個對象的基礎上,這是另一個原因等待和通知在Object類而不是Thread類中聲明。
  3. 在Java中,爲了進入代碼的關鍵部分,線程需要鎖定,並且它們等待鎖定,他們不知道哪些線程持有鎖定,而只是知道鎖定由某個線程持有,而應該等待鎖定知道哪個線程位於同步塊內並要求他們釋放鎖定。這個比喻適合等待並通知對象類而不是Java中的線程。

打個比方: Java線程是一個用戶和馬桶的哪個線程希望執行代碼塊。 Java提供了一種方法來鎖定當前正在使用synchorinized keywokd執行的線程的代碼,並讓其他希望使用它的線程等待第一個線程完成。這些其他線程處於等待狀態。因爲沒有等待線程的隊列,所以Java不能作爲服務站。任何一個正在等待的線程都可能會接收到監視器,而不管它們要求的順序如何。唯一的保證是所有線程遲早都會使用受監視的代碼。

Source

如果你看看下面的生產者和消費者代碼:
sharedQueue對象充當producer and consumer線程之間的線程間通信。

import java.util.Vector; 
import java.util.logging.Level; 
import java.util.logging.Logger; 

public class ProducerConsumerSolution { 

    public static void main(String args[]) { 
     Vector<Integer> sharedQueue = new Vector<Integer>(); 
     int size = 4; 
     Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer"); 
     Thread consThread = new Thread(new Consumer(sharedQueue, size), "Consumer"); 
     prodThread.start(); 
     consThread.start(); 
    } 
} 

class Producer implements Runnable { 

    private final Vector<Integer> sharedQueue; 
    private final int SIZE; 

    public Producer(Vector<Integer> sharedQueue, int size) { 
     this.sharedQueue = sharedQueue; 
     this.SIZE = size; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 7; i++) { 
      System.out.println("Produced: " + i); 
      try { 
       produce(i); 
      } catch (InterruptedException ex) { 
       Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex); 
      } 

     } 
    } 

    private void produce(int i) throws InterruptedException { 

     // wait if queue is full 
     while (sharedQueue.size() == SIZE) { 
      synchronized (sharedQueue) { 
       System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: " 
         + sharedQueue.size()); 

       sharedQueue.wait(); 
      } 
     } 

     // producing element and notify consumers 
     synchronized (sharedQueue) { 
      sharedQueue.add(i); 
      sharedQueue.notifyAll(); 
     } 
    } 
} 

class Consumer implements Runnable { 

    private final Vector<Integer> sharedQueue; 
    private final int SIZE; 

    public Consumer(Vector<Integer> sharedQueue, int size) { 
     this.sharedQueue = sharedQueue; 
     this.SIZE = size; 
    } 

    @Override 
    public void run() { 
     while (true) { 
      try { 
       System.out.println("Consumed: " + consume()); 
       Thread.sleep(50); 
      } catch (InterruptedException ex) { 
       Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex); 
      } 

     } 
    } 

    private int consume() throws InterruptedException { 
     //wait if queue is empty 
     while (sharedQueue.isEmpty()) { 
      synchronized (sharedQueue) { 
       System.out.println("Queue is empty " + Thread.currentThread().getName() 
            + " is waiting , size: " + sharedQueue.size()); 

       sharedQueue.wait(); 
      } 
     } 

     //Otherwise consume element and notify waiting producer 
     synchronized (sharedQueue) { 
      sharedQueue.notifyAll(); 
      return (Integer) sharedQueue.remove(0); 
     } 
    } 
} 

Source

2

想到用一個活生生的例子,一個洗手間。當你想在你的辦公室使用洗手間時,你有兩種選擇來確保在你使用洗手間時沒有人會來洗手間。

  1. 鎖衛生間的門縫,讓其他人都知道,它的使用由其他人,當他們試圖開門
  2. 轉到每個人在辦公室,他們鎖定在他們的椅子(或表,或無論如何),去洗手間。

您會選擇哪個選項?

是的,在Javaland!也是一樣!

所以在上面的故事,

  • 洗手間=對象要鎖定(即只需要使用)
  • 你的員工同事=你想保留了
其他線程

所以就像在現實生活中一樣,當你有一些私人業務時,你鎖定了這個對象。當你完成這件事時,你放開了鎖!

(是的是的!這是發生什麼事了非常簡單的描述。當然,真正的概念是從這個略有不同,但是這是一個起點)