簡體   English   中英

同步塊鎖定對象並等待/通知

[英]synchronized block locking object and wait/notify

根據我的理解,當我使用同步塊時,它獲取對象上的鎖,並在代碼塊執行完畢后釋放它。 在下面的代碼中

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方法中的同步塊必須等待(不是因為wait(),因為另一個線程獲得了鎖)。 執行完run方法后,主方法是否不會進入其同步塊並等待通知它永遠不會得到? 我在這里誤解了什么?

wait()隱式退出相應的監視器,並在返回時重新輸入:

參見wait()

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

這就是這種同步完全起作用的原因和方式。

是的,有可能在導致線程掛起的wait() notify()之前執行notify() ,因此您需要注意不要發生這種情況。

出於這個原因(和其他原因),通常最好使用java.util.concurrent的更高級別的構造,因為它們通常使您腳下射擊的可能性較小。

您不會在這里看到“永遠等待”的問題,因為您正在調用帶有超時的wait()版本。 因此,在5秒鍾后,即使沒有收到通知,它也會返回。 的“永遠等待”版本的wait()調用確實可能表現出您描述的問題。

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

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

假設run方法正常執行,它將調用notify() ,因為當前沒有其他線程處於等待狀態,因此該方法無效。 即使存在,WAN仍會保持鎖定狀態,直到退出同步的代碼塊為止。 一旦WAN退出該塊,那么Java將通知一個等待線程(如果有,則沒有)。

此時,主執行線程現在獲得了鎖(不再被鎖定)並進入等待狀態。 現在,您已經使用了等待版本,它將等待長達5000毫秒才能繼續。 如果您使用原始版本( wait() ),它將永遠等待,因為沒有其他進程會通知它。

這是示例程序的一個版本,更改后引入了一個測試條件變量的循環。 這樣,您可以避免在線程從等待狀態中喚醒后重新獲取鎖之后對事物狀態的錯誤假設,並且兩個線程之間沒有順序依賴性:

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
    }
}

僅僅由於您指出的原因(順序依賴性,您要依靠競爭條件希望一個線程先於另一個線程獲取監視器)等原因而等待通知還不夠。 一方面,線程可能會在沒有收到通知的情況下從等待中醒來,您根本無法假設有一個通知調用。

當線程等待時,它需要在循環中這樣做,在循環測試中它會檢查某些條件。 另一個線程應設置該條件變量,以便第一個線程可以對其進行檢查。 Oracle教程提出的建議是:

注意:始終在循環中調用wait,以測試正在等待的條件。 不要以為中斷是針對您正在等待的特定條件,還是該條件仍然為真。

其他nitpicks:

  • 在編寫示例時,不需要JVM使對sum變量的更改對主線程可見。 如果您添加了一個同步實例方法來訪問sum變量,或者訪問一個同步塊中的sum,那么將保證主線程可以看到sum的更新值。

  • 查看您的日志記錄,發現InterruptedException異常嚴重,這並不意味着有任何錯誤。 當您在線程上調用中斷,設置其中斷標志,並且該線程當前正在等待或處於睡眠狀態,或者進入一個仍設置了該標志的等待或睡眠方法時,就會導致InterruptedException。 在此答案頂部的玩具示例中,我將異常放在throws子句中,因為我知道這種情況不會發生。

  • 當線程終止時,它發出notifyAll,所有在該對象上等待的內容都會收到(同樣,這是實現聯接的方式)。 最好使用Runnable而不是Thread,部分原因在於。

  • 在此特定示例中,在求和線程上調用Thread#join比調用wait更有意義。

這是改寫為使用join的示例:

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;
        }        
    }
}

Thread#join調用等待,鎖定在線程對象上。 求和線程終止時,它將發送通知,並將其isAlive標志設置為false。 同時,在join方法中,主線程正在等待求和線程對象,它接收通知,檢查isAlive標志,並意識到它不再需要等待,因此可以離開join方法並打印結果。

暫無
暫無

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

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