簡體   English   中英

如何在不是線程的對象上調用 wait() 和 notify() 方法?

[英]How can the wait() and notify() methods be called on Objects that are not threads?

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

當然,這一定是有意義的,因為這兩種方法可用於所有 Java 對象。 有人可以提供解釋嗎? 我無法理解如何使用wait()notify()在線程之間進行通信。

鎖定是關於保護共享數據。

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

任何對象都可以用作內在鎖(意味着與synchronized結合使用)。 通過這種方式,您可以通過向訪問共享數據的方法添加同步修飾符來保護對任何對象的訪問。

對用作鎖的對象調用waitnotify方法。 鎖是一個共享的通信點:

  • 當一個擁有鎖的線程調用notifyAll時,其他等待同一個鎖的線程會得到通知。 當一個擁有鎖的線程調用notify時,等待同一個鎖的線程之一會得到通知。

  • 當有鎖的線程調用wait時,該線程釋放鎖並進入休眠狀態,直到 a) 它收到通知,或 b) 它只是任意喚醒(“虛假喚醒”); 由於這兩個原因之一,等待線程一直停留在等待調用中,直到它被喚醒,然后線程必須重新獲取鎖才能退出等待方法。

請參閱有關受保護塊Oracle 教程,Drop 類是共享數據結構,使用生產者和消費者可運行的線程正在訪問它。 鎖定 Drop 對象控制線程如何訪問 Drop 對象的數據。

線程在 JVM 實現中被用作鎖,建議應用程序開發人員避免將線程用作鎖。 例如, Thread.join文檔說:

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

Java 5 引入了實現java.util.concurrent.locks.Lock顯式鎖。 這些比隱式鎖更靈活; 有類似於等待和通知(等待和信號)的方法,但它們是在條件上,而不是在鎖上。 具有多個條件可以僅針對等待特定類型通知的線程。

您可以使用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();

想想使用一個現實生活中的例子,一個洗手間 當您想在辦公室使用洗手間時,您有兩種選擇,以確保您使用后沒有其他人會來洗手間。

  1. 鎖上洗手間的門,這樣其他人在試圖打開門時就會知道它被其他人使用了
  2. 去找辦公室里的每個人,把他們鎖在椅子(或桌子,或其他什么)上,去洗手間。

你會選擇哪個?

是的,Javaland 也是一樣!

所以在上面的故事中,

  • 洗手間 = 您要鎖定的對象(只有您需要使用)
  • 您的員工同事 = 您想排除的其他線程

所以就像在現實生活中一樣,當你有一些私人事務時,你鎖定那個對象。 當您完成該對象時,您松開鎖!

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

您可以根據需要使用靜態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();
    }
}

}

PS如果我理解錯了你的問題,我很抱歉(英語不是我的母語)

當你在 synchronized 塊中放入一些代碼時:

 sychronized(lock){...}

想要執行此塊內任何內容的線程首先獲取對象上的鎖,並且一次只有一個線程可以執行鎖定在同一對象上的代碼。 任何對象都可以用作鎖,但您應該小心選擇與作用域相關的對象。 例如,當您有多個線程向帳戶添加內容時,它們都有一些代碼負責在一個塊中執行此操作,例如:

sychronized(this){...}

然后沒有同步發生,因為它們都鎖定在不同的對象上。 相反,您應該使用帳戶對象作為鎖。 現在考慮這些線程也有從帳戶中提取的方法。 在這種情況下,可能會出現想要提取某些東西的線程遇到空帳戶的情況。 它應該等到有一些錢並釋放鎖給其他線程以避免死鎖。 這就是等待和通知方法的用途。 在這個例子中,遇到空賬戶的線程釋放鎖並等待來自某個進行存款的線程的信號:

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

當其他線程存入一些錢時,它會向等待同一鎖的其他線程發出信號。 (當然,負責存款和取款的代碼必須在同一個鎖上同步才能工作並防止數據損壞)。

balance += amountToDeposit;
lock.signallAll;

如您所見,方法 wait 和 notify 僅在同步塊或方法中有意義。

在 Java 中所有 Object 都實現了這兩個方法,顯然如果沒有監視器這兩個方法是沒有用的。

實際上, waitnotify成員函數不應該屬於線程,它應該屬於的東西命名為來自posix thread 的條件變量 你可以看看 cpp 如何包裝它,它將它包裝成一個專用的類std::condition_variable

Java 沒有做這種封裝,而是用更高級的方式包裝條件變量: monitor (直接把功能放到Object類中)。

如果您不知道監視器或條件變量,那么一開始確實會讓人們感到困惑。

  1. Wait 和 notify 不僅僅是普通的方法或同步實用程序,更重要的是它們是 Java 中兩個線程之間的通信機制。 如果此機制無法通過任何 java 關鍵字(如同步)使用,則對象類是使它們可用於每個對象的正確位置。 請記住同步和等待通知是兩個不同的領域,不要混淆它們是相同或相關的。 同步是提供互斥和保證Java 類的線程安全,如競爭條件,而wait 和notify 是兩個線程之間的通信機制。
  2. 鎖是基於每個對象提供的,這是在 Object 類而不是 Thread 類中聲明 wait 和 notify 的另一個原因。
  3. 在 Java 中,為了進入代碼的臨界區,線程需要鎖並等待鎖,它們不知道哪個線程持有鎖,而只知道鎖被某個線程持有,它們應該等待鎖而不是知道哪個線程持有鎖線程在同步塊內並要求他們釋放鎖。 這個類比適合等待和通知在對象類上而不是 Java 中的線程。

類比: Java 線程是用戶,廁所是線程希望執行的代碼塊。 Java 提供了一種使用同步密鑰鎖定當前正在執行它的線程的代碼的方法,並使希望使用它的其他線程等待直到第一個線程完成。 這些其他線程處於等待狀態。 Java 不像服務站那樣公平,因為沒有等待線程的隊列。 任何一個等待線程都可以獲取下一個監視器,而不管它們要求的順序如何。 唯一的保證是所有線程遲早都會使用受監控的代碼。

來源

如果您查看以下生產者和消費者代碼:
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);
        }
    }
}

來源

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

對象類是為每個對象提供鎖的正確位置。 假設有一個聯名銀行賬戶,因此多個用戶可以使用同一個賬戶通過多個渠道進行交易。 目前,該賬戶的余額為 1500/-,賬戶中保留的最低余額為 1000/-。 現在,第一個用戶試圖通過 ATM 提取 500/- 的金額,另一個用戶試圖通過刷卡機購買價值 500/- 的任何商品。 這里首先訪問賬戶進行交易的通道首先獲取賬戶的鎖,另一個通道將等待交易完成並釋放賬戶的鎖,因為無法知道哪個通道已經獲得了鎖以及哪個通道正在等待獲取鎖。 因此,鎖定始終應用於帳戶本身而不是頻道。 在這里,我們可以將帳戶視為對象,將通道視為線程。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM