1、介紹
在Java多線程編程中,一個線程可能要等待其他線程的某些操作,才能繼續執行。如果使用wait和notify進行線程間通信,則可以實現線程的等待和喚醒。wait方法使當前線程等待,而notify方法則可以隨機喚醒一個等待該對象的線程。但是,使用notify可能會產生虛假喚醒(spurious wakeup),即線程在未被notify的情況下,也會被喚醒。此時,就要使用notifyAll,它會喚醒所有等待該對象的線程。
2、正文
一、notify和notifyAll的區別
notifyAll方法會喚醒所有等待該對象的線程,而notify只會喚醒其中一個線程。如果喚醒的線程不是想要的線程,則需要再次等待,而這會浪費時間和資源。因此,在多線程編程中,應該儘可能使用notifyAll方法。
二、notifyAll的使用方法
在使用notifyAll方法時,需要注意以下幾個方面:
1、使用notifyAll方法需要獲取對象的鎖
在調用notifyAll方法之前,必須先獲取對象的鎖,否則會拋出IllegalMonitorStateException異常。例如:
synchronized(object){ // ... object.notifyAll(); }
2、notifyAll方法喚醒的線程需要競爭鎖
當線程通過notifyAll方法醒來時,需要競爭對象的鎖才能繼續執行。因此,在喚醒線程之前,應該先釋放對象的鎖。例如:
synchronized(object){ // ... object.notifyAll(); } // 當前線程釋放鎖
3、wait和notifyAll方法要在synchronized代碼塊中使用
wait和notifyAll方法需要在synchronized代碼塊中使用,因為要獲取對象的鎖。如果不在synchronized代碼塊中使用,會拋出IllegalMonitorStateException異常。例如:
synchronized(object){ // ... object.wait(); }
三、notifyAll的實現原理
使用wait和notifyAll方法時,操作系統會對每個對象(object)維護一個wait set和一個entry set。
wait set中包含了所有等待該對象的線程,而entry set則包含了該對象正在佔用的線程。當一個線程調用對象的wait方法時,它會被加入到該對象的wait set中,並釋放對象的鎖。當另一個線程調用對象的notify或notifyAll方法時,它會從該對象的wait set中選擇一個等待時間最長的線程,將其從wait set中移除,並加入到entry set中,從而使其競爭對象的鎖。
使用notify方法時,操作系統會隨機選擇一個等待該對象的線程,將其從wait set中移到entry set中。但是,在某些情況下,會出現虛假喚醒的情況。例如,操作系統可能會在不經意的時候喚醒所有等待該對象的線程,而這些線程並沒有收到notify的通知。這種情況下,notifyAll方法就能夠避免虛假喚醒的問題,因為它會喚醒所有等待該對象的線程。
3、代碼示例
下面是一個使用notifyAll方法的示例代碼:
public class WaitNotify { public static void main(String[] args) { final Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { try { System.out.println("Thread 1 starts waiting"); lock.wait(); System.out.println("Thread 1 is awakened"); } catch(InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(() -> { synchronized (lock) { try { System.out.println("Thread 2 starts waiting"); lock.wait(); System.out.println("Thread 2 is awakened"); } catch(InterruptedException e) { e.printStackTrace(); } } }); Thread t3 = new Thread(() -> { synchronized (lock) { System.out.println("Thread 3 starts notifying"); lock.notifyAll(); } }); t1.start(); t2.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t3.start(); } }
運行結果:
Thread 1 starts waiting Thread 2 starts waiting Thread 3 starts notifying Thread 2 is awakened Thread 1 is awakened
總結
在Java多線程編程中,notifyAll方法是實現線程間通信的重要方式之一。在使用notifyAll方法時,需要注意鎖的獲取和釋放、wait和notifyAll方法的使用等方面,以避免出現問題。同時,notifyAll方法在遇到虛假喚醒問題時也能夠提供一種有效的解決方案。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/309492.html