在開始這個話題之前,我們先聊一下線程。線程的目的就是爲了讓一些程序片段能夠“同時”進行,至少是視覺上。因爲實際上,這些程序片段是“交替”執行的。如圖1
在途中,線程A和線程B輪流交替地佔有CPU的處理時間。也許有人說,CPU總的計算量並沒有降低啊,確實如此,還可以切換線程,還增加了一點點的計算量。如果現在有一個很長很複雜很費時的計算任務,和一個精彩電影的播放任務,那我們當然願意選擇讓這兩個不太相干的任務同時併發執行。由此可見線程的目的是用一些調度算法,使電腦用戶不會“等待”的不耐煩。因爲絕大多數的用戶都希望計算機體貼周到,富有人性化,提供更好的服務。事實上,我們在計算機上做的很多革新,就是爲了如此。
Java是支持線程的,不像C++,還需要操作系統的配合才行,當然java最終還是要用到操作系統底層的支持,纔可以擁有多線程編程的能力,呵呵。Java實現線程非常簡單。一個是繼承Thread,一個是實現Runnable接口。我突然想起我畢業時面試時經常被問這樣的問題,那時只不過是機械式的回答,裏面有許多的道理倒沒有現在認識深刻。我們需要併發的代碼寫在run方法裏面就OK了,呵呵。至於java底層是如何壓棧入棧,保護程序執行現場的,如何恢復現場的,我們就不必深究了,因爲我們不是API級的程序員,我們需要的是學習別人的成果,迅速copy,快速開發,不然怎麼超英趕美啊?我們的目的是爲了圖2中的“順序執行”變成圖1中的“併發執行”。其中圖1中的“時間片”的劃分是隨機的。
Java的線程執行,也是非常簡單的,new 一個線程,然後start就OK了。
關於Thread這個類,有時候容易混淆。打個比方說,使用這個類可以類比成英語裏面的語法“Somebody do Something”。舉個例子:
SomeBody類:
public class SomeBody {
private String name;
public SomeBody(String name)
{
this.name=name;
}
public void someBodyDo()
{
System.out.println(this.name+"早上7:45起牀");
//隨機生成停頓時間,爲了模擬較長的代碼執行時間,以下都是類似
try {
int a=(int) ((long)1000* Math.random());
System.out.println(a);
Thread.sleep(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name+"7:50大便");
try {
int a=(int) ((long)1000* Math.random());
System.out.println(a);
Thread.sleep(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name+"8:00刷牙,順帶刷刷舌苔");
try {
int a=(int) ((long)1000* Math.random());
System.out.println(a);
Thread.sleep(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name+"8:05洗臉");
try {
int a=(int) ((long)1000* Math.random());
System.out.println(a);
Thread.sleep(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name+"8:10搽雪花膏");
try {
int a=(int) ((long)1000* Math.random());
System.out.println(a);
Thread.sleep(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name+"8:20到車站");
try {
int a=(int) ((long)1000* Math.random());
System.out.println(a);
Thread.sleep(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name+"8:30搭上車");
try {
int a=(int) ((long)1000* Math.random());
System.out.println(a);
Thread.sleep(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name+"8:40到公司吃飯");
try {
int a=(int) ((long)1000* Math.random());
System.out.println(a);
Thread.sleep(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name+"8:50打開電腦發呆");
try {
int a=(int) ((long)1000* Math.random());
System.out.println(a);
Thread.sleep(a);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.name+"9:00開始工作");
}
}
具有“打亂”代碼執行順序能力的Thead子類:SomeBodyWork類:
package wjj2;
public class SomeBodyWork extends Thread{
private SomeBody person;
public SomeBodyWork (SomeBody person)
{
this.person=person;
}
public void run()
{
person.someBodyDo();
}
}
程序入口類:main類:
public class main {
public static void main(String args[])
{
SomeBody xiaoming=new SomeBody("小明 ");
SomeBodyWork thread1=new SomeBodyWork(xiaoming);
thread1.start();
SomeBody dagang=new SomeBody("大剛 ");
SomeBodyWork thread2=new SomeBodyWork(dagang);
thread2.start();
}
}
程序執行結果如下:
571
大剛 早上7:45起牀
908
小明 7:50大便
395
大剛 7:50大便
583
小明 8:00刷牙,順帶刷刷舌苔
509
小明 8:05洗臉
25
大剛 8:00刷牙,順帶刷刷舌苔
224
小明 8:10搽雪花膏
962
大剛 8:05洗臉
292
大剛 8:10搽雪花膏
665
小明 8:20到車站
554
大剛 8:20到車站
278
大剛 8:30搭上車
934
小明 8:30搭上車
577
小明 8:40到公司吃飯
238
小明 8:50打開電腦發呆
812
大剛 8:40到公司吃飯
453
大剛 8:50打開電腦發呆
361
小明 9:00開始工作
大剛 9:00開始工作
通常Somebody自成一個單獨的類,線程的目的就是:讓 Somebody 併發地 doSomething。
從程序的結果來看:
1.兩個線程的代碼確實像圖1一樣,交替執行,且交替順序是隨機的,多試幾次會發現執行結果會有不同。
2.線程的run()方法提供了一種機制,一種把代碼執行順序“打亂”的機制,有些初學者喜歡在這個提供這個機制的類中,寫一下“實體”類的方法,比如,他會讓SomeBody類在SomeBodyWork中做一些事情,如
System.out.println(this.name+"早上10:00開始編碼");
System.out.println(this.name+"早上11:00開始測試");
System.out.println(this.name+"下午14:45開始修改代碼");
雖然Java中,什麼東西都是對象,什麼東西都以對象的模板“類”來展現,但類和類之間,還是有區別的,
很顯然,以上3行代碼,還是屬於“實體”這些類的方法,應該移植在“實體”類中,Thread這個類,提供了一種機制,這種機制用“類”來展現,很神奇吧,呵呵。所以最好不要把雜七雜八的東東放在Thread的子類中,就是一個run()最好,Thread就是純粹用來“併發”地run的。關於代碼的歸類,可以參考《重構--改善既有代碼的設計》,Martin Fowler著,侯捷,熊節譯,中國電力出版社。