Java多執行緒程式設計總結筆記——01 Java語言的執行緒
GUI應用程式
幾乎所有的GUI應用程式都會用多執行緒。舉例來說加入現在有人在用word編輯一個比較大的文字檔案剛剛才做過單字“查詢”操作,當word進行查詢時,螢幕上會出現“停止查詢按鈕”,使用者可以隨時停止查詢。這個功能其實就用到了多執行緒。
(1)執行查詢
(2)顯示按鈕,若按鈕按下則停止查詢
這兩個操作分別交給不同的執行緒進行。這樣一來執行執行緒(1)的執行緒可以專心查詢,執行(2)的執行緒也可專心在GUI操作上,程式就會變得比較簡單。
比較花費時間的I/O操作
多個客戶端
基本上網路上的伺服器必須同時處理一個以上的客戶端,不過,一定要在伺服器這邊的程式設計加入一個以上客戶端的概念的話,程式會變得更復雜。此時,不妨準備一個當有客戶端連線到伺服器的時候,會自動出來迎接這個客戶點的執行緒,這樣一來,伺服器的程式就可以設計成好像只服務一個客戶端
併發與並行
當有一個以上的執行緒的執行緒來操作時,若計算機只有一箇中央處理器,根本不可能進行一個以上的處理。
如果在有一個以上的中央處理器的計算機上跑程式,則執行緒的程式可能是並行(parallel)而非併發,就可以同時執行一個以上的處理。
執行緒的啟動
/** * @Title: Thread1.java * @Package: org.lenvon.thread * @Description: TODO * @author: Lenvon * @date: 2015年6月17日 下午3:06:51 * @version: V1.0 */ package org.lenvon.thread; /** * @moudle: Thread1 * @version:v1.0 * @Description: TODO * @author: Lenvon * @date: 2015年6月17日 下午3:06:51 * */ public class Thread01 { static class PrintThread extends Thread { private String msg ; /** * <p>Title: </p> * <p>Description: </p> * @param msg */ public PrintThread(String msg) { this.msg = msg; } /** * * <p>Title: run</p> * <p>author : Lenvon</p> * <p>date : 2015年6月17日 下午3:08:40</p> */ @Override public void run() { // TODO Auto-generated method stub System.out.println(msg); } } /** * * <p>Title: main</p> * <p>author : Lenvon</p> * <p>date : 2015年6月17日 下午3:06:51</p> * * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new PrintThread("Good").start(); new PrintThread("Nice").start(); System.out.println("End---------------"); } }
“PrintThread的例項”和“執行緒本身”是兩個不同的部分,即使建立了“PrintThread的例項”,也還沒有啟動執行緒,而且就算執行緒已經結束,PrintThread的例項也不會就這樣消失不見。別忘了,在字串輸出結束之前,已經啟動的兩個執行緒還活著,一直要等到所有執行緒都已經結束,程式才會結束。當所有執行緒都結束,這個程式才會正式結束。
Thread類和Runnable介面
Thread類也實現了Runnable 介面,也有run()方法,只不過Thread類的run()方法的主體是空的沒有執行任何部分。Thread類的run()方法通常是被子類覆蓋(override)
Synchronized例項方法和Synchronized阻擋
假設現在有一個型別如下的synchronized例項方法
synchronized void method(){
…
}
在功能上和下面以synchronized阻擋為主的方法有異曲同工之妙。
void method(){
Synchronized(this){
…
}
}
換句話說synchronized方法是使用this鎖去做執行緒的共享互斥。
Synchronized類方法和Synchronized阻擋
假設現在有一個型別如下的synchronized的類方法,synchronized類方法有限制同時只能讓一個執行緒執行。這部分和synchronized例項方法一樣,但是兩者是有不同的。
Class Something{
static synchronized voidmethod(){
…
}
}
在功能上和下面以synchronized阻擋為主的方法有異曲同工之妙。
Class Something{
static void method(){
synchronized(Something.class){
…
}
}
}
換句話說synchronized的類方法是使用該類的類物件的鎖去做執行緒的共享互斥。Something.class是對應Something類的java.lang.Class類的例項。
Wait方法——把執行緒放入wait set
使用wait方法時,執行緒便進入wait set,假設現在已經執行如下語句:obj.wait();
則目前的執行緒停止執行,進入例項obj的的wait set.這個操作成為:執行緒在obj上wait.
如果例項方法還有如下的語句時:wait();
則其意義同:this.wait();
故執行wait的執行緒就會進入this的wait set.此時就變成了在this上wait.
如欲執行wait()方法,執行緒需獲取鎖定(這是規則)。但是當執行緒進入wait set時,已經釋放了該例項的鎖定。
Notify方法——從wait set拿出執行緒
使用notify()(通知)方法時,可以從wait set拿出一個執行緒。
obj.nitify();則從wait set裡的執行緒中挑出一個,喚醒這個執行緒。被喚醒的執行緒便退wait set
Notify後的執行緒
被notify喚醒的執行緒不是在notify後立即執行,因為在notify的那一刻,執行notify 的執行緒還握著鎖定不放,所以其他執行緒無法獲取該例項的鎖定。
Notify如何選擇執行緒
假設執行notify方法時,wait set裡面正在執行的執行緒不止一個。規格並沒有註明此時該選擇哪一個執行緒。究竟是選擇等待執行緒裡面的第一個,隨機選擇或是另以其他方式選擇,則以java處理系統而異。所以在寫程式時,程式屬性最好不要寫成會因所選執行緒而有所變動。
notifyAll()方法——從wait set 拿出所有執行緒
使用notifyAll(通知全體)方法時,會將所有在waitset苦等的執行緒都拿出來。
obj.notifyAll()則會喚醒所有留在例項obj的wait set裡的執行緒。
而notifyAll();則其意義同this.notifyAll();故這個語句所在方法的例項(this)的wait set裡的執行緒會全部放出來。
跟wait方法和notify方法一樣,執行緒必須要獲取要呼叫例項的鎖定,才能呼叫notifyAll方法。
被喚醒的執行緒便開始去獲取剛才wait時釋放掉的鎖定,那麼現在這個鎖定現在是在誰的手中呢?沒錯,鎖定就是在剛才執行notifyAll方法的程式手裡,因此即使所有執行緒都退出了wait set,但他們仍然在去獲得鎖定的狀態下,還是有阻擋。要等到剛才執行notifyAll方法的執行緒釋放出鎖定後,其中一名幸運兒才會實際執行。
要是沒有鎖定呢
若沒有鎖定的執行緒去呼叫wait,notify或notifyAll時,便會丟擲異常java.lang.IllegalMonitorStateException.
呼叫notify方法還是notifyAll方法
Notify方法和notifyAll方法兩者非常相似,到底該用哪一個?老實說,這個選擇有點難。選擇notify的話,因為要喚醒的執行緒比較少,程式處理速度當然要比notifyAll略勝一籌。但是選擇notify時,若這部分程式處理的不好,可能會有程式掛掉的危險性,一般說來,選擇notifyAll所寫出來的程式程式碼要比選擇notify可靠。除非你能確定程式設計師對程式程式碼的意義和能力限度一清二楚,否則選擇notifyAll應該比較穩紮穩打