線程掛起和喚醒,Object 的 wait 和 sinal

wait() 和 notify() 顧名思義一個是等待,一個是喚醒。分別表示線程的掛起和喚醒。
兩者都是Object類的方法。

wait() 使用的時候必須注意在 synchronized 的代碼塊裏執行。

因爲執行 wait 函數之前會先獲取到目標對象的監視器,然後釋放監視器,方便其他等待在這個目標對象上的線程可以繼續執行下去。

package demo1;

public class SimpleWN {
	
	final static Object object = new Object(); //synchronized的目標對象,就是鎖
	
	public static class T1 extends Thread {

		public void run() {
			synchronized(object) {
				System.out.println(System.currentTimeMillis()+": T1 start!");
				try {
					System.out.println(System.currentTimeMillis()+": T1 wait for object!");
					object.wait(); //線程掛起,停止執行,並且會把鎖釋放
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(System.currentTimeMillis()+": T1 end!");
				
			}
		}
		
	}
	
	public static class T2 extends Thread {

		public void run() {
			synchronized(object) {

				System.out.println(System.currentTimeMillis()+": T2 start! notify one thread");
				object.notify(); //隨機喚醒一個等待在object對象上的線程。
				System.out.println(System.currentTimeMillis()+": T2 end!");
				
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
		}
		
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new T1();
//		Thread t11 = new T1();
		
		Thread t2 = new T2();
		t1.start();
//		t11.start();
		
		Thread.sleep(2000);
		t2.start();
	}
	
}

和notify() 類似的有一個 notifyAll() 函數。

兩者有些區別:

  • notify() 會有jvm隨機選擇一個線程喚醒,這個線程等待鎖後就可以執行,其他線程仍舊掛起,除非繼續喚醒

  • notifyAll() 會喚醒所有等待在這個鎖上的線程,這些線程只要拿到鎖後就可以執行

suspend() 和 resume() 是 Thread 類提供的一對用於線程的掛起和喚醒的api。

不過這對api已經被標明不建議使用,原因在於 suspend 掛起線程的時候,線程不會釋放任何的鎖。如果這個線程不被喚醒,那麼將永遠的佔有鎖,導致等待這個鎖的其他線程全部執行不下去。

和 wait 的區別:

  • wait() 一定要在 synchronized 代碼塊中使用,因爲要獲得鎖並且釋放; suspend 不需要在synchronized 代碼塊中使用,也不會釋放線程佔有的鎖。
  • wait 掛起的線程狀態爲 WAITING,suspend 掛起的線程狀態爲 RUNNABLE。這個線程狀態對於查找問題的時候是會有誤導的。

下面是使用的例子,並且模擬了 suspend 不釋放鎖的弊端

package demo1;

public class BadSuspend {
	
	public static Object object = new Object();
	
	
	
	public static class ChangeObjectThread extends Thread {
		
		public ChangeObjectThread(String name) {
			super.setName(name);
		}
		
		@Override
		public void run() {

			
			synchronized(object) {
				System.out.println("in " + getName());
				Thread.currentThread().suspend();  //掛起當前線程,但是不會釋放鎖 object
			}
			
		}
		
		
	}
	
	static ChangeObjectThread t1 = new ChangeObjectThread("t1");
	static ChangeObjectThread t2 = new ChangeObjectThread("t2");
	
	
	public static void main(String[] args) throws InterruptedException {
		t1.start();
		Thread.sleep(100);
		t2.start();
		
		t1.resume(); //喚醒 t1 線程,這個線程 resume 執行的順序一般在 suspend 後面,因爲上面 sleep 了 100 ms.但是這也不是絕對的。兩個線程同步執行順序不可控
		t2.resume(); //喚醒 t2 線程, 因爲兩個線程同步執行,resume 很有可能在線程裏的 suspend 之前執行。導致線程錯過被喚醒,而 object 鎖被一直佔用。假設有其它線程等待在這個鎖上,那麼將永遠無法執行
		
		Thread.sleep(1000);
		
		System.out.println(t1.getState().toString());
		System.out.println(t2.getState().toString());
		
		t1.join();
		t2.join();
	}
	
	
}

輸出結果如下,並且程序無法結束,因爲程序掛起後錯過了被喚醒的時機

in t1
in t2
TERMINATED
RUNNABLE
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章