java中線程阻塞之sleep、suspend、join、wait、resume、notify方法解析(一)

java線程的5狀態包括create、runnable、running、blocked、dead。

create是指用new線程被創建,但是還沒有準備好各種資源。

runnable是指使用start啓動線程,這時候系統會爲線程分配除cpu以外的所有需要的資源。

running是指cpu只會調度處於runnable狀態的線程使其佔用cpu時間片真正開始運行。

blocked是指由於某種原因導致running中的線程暫停執行,被放到阻塞隊列中,cpu不會再給他們分配時間片,直到導致阻塞的原因被解除而變爲runnable狀態。

dead指線程結束,包括正常結束(如執行完run方法)和非正常結束(如拋出異常等)。

那麼造成blocked的原因有哪些呢?

(1) 調用 sleep(毫秒數),使線程進入“睡眠”狀態。在規定的時間內,這個線程是不會運行的。
(2) 用 suspend()暫停了線程的執行。除非線程收到 resume() 消息,否則不會返回“可運行”狀態。
(3) 用 wait()暫停了線程的執行。除非線程收到 nofify() 或者 notifyAll()消息,否則不會變成“可運行”(是的,看起來同原因 2 非常相象,但有一個明顯區別是我們馬上要揭示的)
(4) 線程正在等候一些 IO(輸入輸出)操作完成。
(5) 線程試圖調用另一個對象的“同步”方法,但那個對象處於鎖定狀態,暫時無法使用。


先寫兩個基類:

class Blockable extends Thread {
	private Peeker peeker;
	protected int i;
	public Blockable(){
		peeker = new Peeker(this);
	}
	
	public synchronized int read(){ return i;}
	
	public synchronized void update(){
		System.out.println(this.getClass().getName()+"state :i = " + i);
	}
	
	public void stopPeeker(){
		peeker.terminate();
	}
}

class Peeker extends Thread {
	private Blockable b;
	private int session;
	private boolean stop = false;
	
	public Peeker(Blockable b){
		this.b = b;
		start();
	}
	
	public void run(){
		while (!stop) {
			System.out.println(b.getClass().getName()
			+ " Peeker " + (++session)
			+ "; value = " + b.read());
			try {
				sleep(1000);
			} catch (InterruptedException e){}
		}
	}
	
	public void terminate() {stop = true;}
	
}

Blockable 類打算成爲本例所有類的一個基礎類。一個 Blockable 對象包含了一個i信息。用於顯示這些信息的方法叫作 update() 。我們發現它用
getClass.getName() 來產生類名,而不是僅僅把它打印出來;這是由於 update()不知道自己爲其調用的那個類的準確名字,因爲那個類是從 Blockable 衍生出來的。 

在 Blockable 中,變動指示符是一個 int i;衍生類的 run()方法會爲其增值。
針對每個 Bloackable 對象,都會啓動 Peeker 類的一個線程。Peeker 的任務是調用 read()方法,檢查與自己
關聯的 Blockable 對象,看看 i 是否發生了變化,最後打印檢查結果。注意 read()和 update() 都是同步的,要求對象的鎖定能自由解除,這一點非常重要。

(1)sleep方法的調用阻塞自己時,也會引起其他線程阻塞是因爲sleep不會釋放對象鎖。下是測試代碼:

class Sleeper1 extends Blockable {

	public Sleeper1() {
		super();
	}
	
	public synchronized void run() {
		while(true) {
			i++;
			update();
			try {
				sleep(1000);
			} catch (InterruptedException e){}
		}
	}
}

class Sleeper2 extends Blockable {
	public Sleeper2() {
		super();
	}
	
	public void run() {
		while(true) {
			change();
			try {
				sleep(1000);
			} catch (InterruptedException e){}
		}
	}
	
	private synchronized void change(){
		i++;
		update();
	}
}
public class SleepBlock {

	public static void main(String[] args) {
		new Sleeper1().start();
		new Sleeper2().start();
	}

}
我們會發現Sleeper1中的run方法是同步的,由於run方法裏面是死循環,當Sleeper1線程啓動後,Sleeper1內的Peeker線程根本無法運行。因爲Sleeper1的sleep方法並不會釋放對象鎖。

      在 Sleeper1 中,整個 run()方法都是同步的。我們可看到與這個對象關聯在一起的 Peeker 可以正常運行,直到我們啓動線程爲止,隨後 Peeker 便會完全停止。這正是“堵塞”的一種形式:因爲 Sleeper1.run()是同步的,而且一旦線程啓動,它就肯定在 run()內部,方法永遠不會放棄對象鎖定,造成 Peeker 線程的堵塞。
     Sleeper2 通過設置不同步的運行,提供了一種解決方案。只有 change() 方法纔是同步的,所以儘管 run()位於 sleep()內部,Peeker 仍然能訪問自己需要的同步方法——read()。在這裏,我們可看到在啓動了Sleeper2 線程以後,Peeker 會持續運行下去。


(2)suspend方法調用阻塞自己時,引起其他線程阻塞也是因爲suspend方法不會釋放對象鎖,這是不安全的。此方法已經廢棄。因爲使用suspend會發生一種很愚蠢的現象,就是自己把自己掛起,也就是進入阻塞狀態。但是還搶着對象鎖(前提是有同步方法訪問同樣的資源),這樣其他線程來訪問同步方法也只能傻傻等待。就好像你自己拿着鑰匙在那等別人來開門,別人來開你又不給鑰匙。自己阻塞後還不讓別人獲得鎖從而執行,讓別人也跟着在那兒等。


(3)wait方法阻塞自己時會釋放對象鎖,因此不會引起其他線程因得不到鎖而阻塞,這一點和sleep、suspend是根本區別,因此他不會阻礙其他線程。

若必須等候其他某些條件(從線程外部加以控制)發生變化,同時又不想在線程內一直傻乎乎地等下去,一般就需要用到 wait()。wait()允許我們將線程置入“睡眠”狀態,同時又“積極”地等待條件發生改變。而且只有在一個 notify() 或 notifyAll()發生變化的時候,線程纔會被喚醒,並檢查條件是否有變。因此,我們認爲它提供了在線程間進行同步的一種手段。

我們也可以看到 wait()的兩種形式。第一種形式採用一個以毫秒爲單位的參數,它具有與 sleep()中相同的含義:暫停這一段規定時間。區別在於在 wait()中,對象鎖已被解除,而且能夠自由地退出 wait(),因爲一個 notify() 可強行使時間流逝。第二種形式不採用任何參數,這意味着 wait()會持續執行,直到 notify() 介入爲止。而且在一段時間以後,不會自行中止。


(4)IO引起的阻塞。這一部分主要是IO那一塊的。由於訪問IO可能會等待,因此會引起阻塞。


(5)在上面已經可以看出來了,不同線程在訪問相同對象的同步方法時,會引起阻塞。需要等待一方先完成才能進行。


另外還有join()和yield()方法,

其中join的使用方式是threadname.join(),他的作用是讓當前線程(注意當前線程不是threadname,而是執行這個語句的線程)阻塞,等待threadname線程執行完之後再執行,這也是我們所說的線程合併,因爲它相當於把本來兩條單獨的執行路線合併到一起了。


但是yeild()方法根本都不是阻塞,他是讓線程主動放棄cpu,等待下次調度,並沒有被放入阻塞隊列。

最後,如果一個程序線程很多,某些線程阻塞之後要想讓其儘快解除阻塞去運行,就需要儘快讓阻塞解除的條件發生。這時,需要弄清楚阻塞條件,如果有其他線程能加快該阻塞線程的阻塞解除速度,那麼可以調整其他未阻塞線程的優先級,讓未阻塞線程儘快多的執行從而加速阻塞線程解除阻塞,這裏也就是動態調整線程優先級的方法來提高整個系統的運行效率。如果是IO阻塞,例如網絡阻塞,那麼這個時候可以從網絡方面入手,如果是文件阻塞可以從磁盤IO入手。


發佈了39 篇原創文章 · 獲贊 10 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章