一、Java環(huán)境下的多線程技術(shù)
構(gòu)建線程化的應(yīng)用程序往往會(huì)對程序帶來重要的性能影響。例如,請考慮這樣一個(gè)程序,它從磁盤讀取大量數(shù)據(jù)并且在把它們寫到屏幕之前處理這些數(shù)據(jù)(例如一個(gè)DVD播放器)。在一個(gè)傳統(tǒng)的單線程程序(今天所使用的大多數(shù)客戶端程序)上,一次只有一個(gè)任務(wù)執(zhí)行,每一個(gè)這些活動(dòng)分別作為一個(gè)序列的不同階段發(fā)生。只有在一塊已定義大小的數(shù)據(jù)讀取完成時(shí)才能進(jìn)行數(shù)據(jù)處理。因此,能處理數(shù)據(jù)的程序邏輯直到磁盤讀操作完成后才得到執(zhí)行。這將導(dǎo)致非常差的性能問題。
在一個(gè)多線程程序中,可以分配一個(gè)線程來讀取數(shù)據(jù),讓另一個(gè)線程來處理數(shù)據(jù),而讓第三個(gè)線程把數(shù)據(jù)輸送到圖形卡上去。這三個(gè)線程可以并行運(yùn)行;這樣以來,在磁盤讀取數(shù)據(jù)的同時(shí)仍然可以處理數(shù)據(jù),從而提高了整體程序的性能。許多大量的示例程序都可以被設(shè)計(jì)來同時(shí)做兩件事情以進(jìn)一步提高性能。Java虛擬機(jī)(JVM)本身就是基于此原因廣泛使用了多線程技術(shù)。
本文將討論創(chuàng)建多線程Java代碼以及一些進(jìn)行并行程序設(shè)計(jì)的練習(xí);另外還介紹了對開發(fā)者極為有用的一些工具和資源。篇幅所限,不可能全面論述這些問題,所以我想只是重點(diǎn)提一下極重要的地方并提供給你相應(yīng)的參考信息。
二、線程化Java代碼
所有的程序都至少使用一個(gè)線程。在C/C++和Java中,這是指用對main()的調(diào)用而啟動(dòng)的那個(gè)線程。另外線程的創(chuàng)建需要若干步驟:創(chuàng)建一個(gè)新線程,然后指定給它某種工作。一旦工作做完,該線程將自動(dòng)被JVM所殺死。
Java提供兩個(gè)方法來創(chuàng)建線程并且指定給它們工作。第一種方法是子類化Java的Thread類(在java.lang包中),然后用該線程的工作函數(shù)重載run()方法。下面是這種方法的一個(gè)示例:
public class SimpleThread extends Thread { public SimpleThread(String str) { super(str); } public void run() { for (int i = 0; i < 10; i++) { System.out.println(i + " " + getName()); try { sleep((long)(Math.random() * 1000)); } catch (InterruptedException e) {} } System.out.println("DONE! " + getName()); }}
這個(gè)類子類化Thread并且提供它自己的run()方法。上面代碼中的函數(shù)運(yùn)行一個(gè)循環(huán)來打印傳送過來的字符串到屏幕上,然后等待一個(gè)隨機(jī)的時(shí)間數(shù)目。在循環(huán)十次后,該函數(shù)打印"DONE!",然后退出-并由它殺死這個(gè)線程。下面是創(chuàng)建線程的主函數(shù):
public class TwoThreadsDemo { public static void main (String[] args) { new SimpleThread("Do it!").start(); new SimpleThread("Definitely not!").start(); }}
注意該代碼極為簡單:函數(shù)開始,給定一個(gè)名字(它是該線程將要打印輸出的字符串)并且調(diào)用start()。然后,start()將調(diào)用run()方法。程序的結(jié)果如下所示:
0 Do it!0 Definitely not!1 Definitely not!2 Definitely not!1 Do it!2 Do it!3 Do it!3 Definitely not!4 Do it!4 Definitely not!5 Do it!5 Definitely not!6 Do it!7 Do it!6 Definitely not!8 Do it!7 Definitely not!8 Definitely not!9 Do it!DONE! Do it!9 Definitely not!DONE! Definitely not!
正如你所看到的,這兩個(gè)線程的輸出結(jié)果糾合到一起。在一個(gè)單線程程序中,所有的"Do it!"命令將一起打印,后面跟著輸出"Definitely not!".
這個(gè)程序的不同運(yùn)行將產(chǎn)生不同的結(jié)果。這種不確定性來源于兩個(gè)方面:在循環(huán)中有一個(gè)隨機(jī)的暫停;更為重要的是,因?yàn)榫€程執(zhí)行時(shí)間沒法保證。這是一個(gè)關(guān)鍵的原則。JVM將根據(jù)它自己的時(shí)間表運(yùn)行這些進(jìn)程(虛擬機(jī)一般支持盡可能快地運(yùn)行這些線程,但是沒法保證何時(shí)運(yùn)行一個(gè)給定線程)。對于每個(gè)線程可以使一個(gè)優(yōu)先級與之相關(guān)聯(lián)以確保關(guān)鍵線程被JVM處理在次要的線程之前。
啟動(dòng)一個(gè)線程的第二種方法是使用一個(gè)實(shí)現(xiàn)Runnable接口的類-這個(gè)接口也定義在java.lang中。這個(gè)Runnable接口指定一個(gè)run()方法-然后該方法成為線程的主函數(shù),類似于前面的代碼。
現(xiàn)在,Java程序的一般風(fēng)格是支持繼承的接口。通過使用接口,一個(gè)類在后面仍然能夠繼承(子類化)-如果必要的話(例如,如果該類要在后面作為一個(gè)applet使用的話,就會(huì)發(fā)生這種情況)。
構(gòu)建線程化的應(yīng)用程序往往會(huì)對程序帶來重要的性能影響。例如,請考慮這樣一個(gè)程序,它從磁盤讀取大量數(shù)據(jù)并且在把它們寫到屏幕之前處理這些數(shù)據(jù)(例如一個(gè)DVD播放器)。在一個(gè)傳統(tǒng)的單線程程序(今天所使用的大多數(shù)客戶端程序)上,一次只有一個(gè)任務(wù)執(zhí)行,每一個(gè)這些活動(dòng)分別作為一個(gè)序列的不同階段發(fā)生。只有在一塊已定義大小的數(shù)據(jù)讀取完成時(shí)才能進(jìn)行數(shù)據(jù)處理。因此,能處理數(shù)據(jù)的程序邏輯直到磁盤讀操作完成后才得到執(zhí)行。這將導(dǎo)致非常差的性能問題。
在一個(gè)多線程程序中,可以分配一個(gè)線程來讀取數(shù)據(jù),讓另一個(gè)線程來處理數(shù)據(jù),而讓第三個(gè)線程把數(shù)據(jù)輸送到圖形卡上去。這三個(gè)線程可以并行運(yùn)行;這樣以來,在磁盤讀取數(shù)據(jù)的同時(shí)仍然可以處理數(shù)據(jù),從而提高了整體程序的性能。許多大量的示例程序都可以被設(shè)計(jì)來同時(shí)做兩件事情以進(jìn)一步提高性能。Java虛擬機(jī)(JVM)本身就是基于此原因廣泛使用了多線程技術(shù)。
本文將討論創(chuàng)建多線程Java代碼以及一些進(jìn)行并行程序設(shè)計(jì)的練習(xí);另外還介紹了對開發(fā)者極為有用的一些工具和資源。篇幅所限,不可能全面論述這些問題,所以我想只是重點(diǎn)提一下極重要的地方并提供給你相應(yīng)的參考信息。
二、線程化Java代碼
所有的程序都至少使用一個(gè)線程。在C/C++和Java中,這是指用對main()的調(diào)用而啟動(dòng)的那個(gè)線程。另外線程的創(chuàng)建需要若干步驟:創(chuàng)建一個(gè)新線程,然后指定給它某種工作。一旦工作做完,該線程將自動(dòng)被JVM所殺死。
Java提供兩個(gè)方法來創(chuàng)建線程并且指定給它們工作。第一種方法是子類化Java的Thread類(在java.lang包中),然后用該線程的工作函數(shù)重載run()方法。下面是這種方法的一個(gè)示例:
public class SimpleThread extends Thread { public SimpleThread(String str) { super(str); } public void run() { for (int i = 0; i < 10; i++) { System.out.println(i + " " + getName()); try { sleep((long)(Math.random() * 1000)); } catch (InterruptedException e) {} } System.out.println("DONE! " + getName()); }}
這個(gè)類子類化Thread并且提供它自己的run()方法。上面代碼中的函數(shù)運(yùn)行一個(gè)循環(huán)來打印傳送過來的字符串到屏幕上,然后等待一個(gè)隨機(jī)的時(shí)間數(shù)目。在循環(huán)十次后,該函數(shù)打印"DONE!",然后退出-并由它殺死這個(gè)線程。下面是創(chuàng)建線程的主函數(shù):
public class TwoThreadsDemo { public static void main (String[] args) { new SimpleThread("Do it!").start(); new SimpleThread("Definitely not!").start(); }}
注意該代碼極為簡單:函數(shù)開始,給定一個(gè)名字(它是該線程將要打印輸出的字符串)并且調(diào)用start()。然后,start()將調(diào)用run()方法。程序的結(jié)果如下所示:
0 Do it!0 Definitely not!1 Definitely not!2 Definitely not!1 Do it!2 Do it!3 Do it!3 Definitely not!4 Do it!4 Definitely not!5 Do it!5 Definitely not!6 Do it!7 Do it!6 Definitely not!8 Do it!7 Definitely not!8 Definitely not!9 Do it!DONE! Do it!9 Definitely not!DONE! Definitely not!
正如你所看到的,這兩個(gè)線程的輸出結(jié)果糾合到一起。在一個(gè)單線程程序中,所有的"Do it!"命令將一起打印,后面跟著輸出"Definitely not!".
這個(gè)程序的不同運(yùn)行將產(chǎn)生不同的結(jié)果。這種不確定性來源于兩個(gè)方面:在循環(huán)中有一個(gè)隨機(jī)的暫停;更為重要的是,因?yàn)榫€程執(zhí)行時(shí)間沒法保證。這是一個(gè)關(guān)鍵的原則。JVM將根據(jù)它自己的時(shí)間表運(yùn)行這些進(jìn)程(虛擬機(jī)一般支持盡可能快地運(yùn)行這些線程,但是沒法保證何時(shí)運(yùn)行一個(gè)給定線程)。對于每個(gè)線程可以使一個(gè)優(yōu)先級與之相關(guān)聯(lián)以確保關(guān)鍵線程被JVM處理在次要的線程之前。
啟動(dòng)一個(gè)線程的第二種方法是使用一個(gè)實(shí)現(xiàn)Runnable接口的類-這個(gè)接口也定義在java.lang中。這個(gè)Runnable接口指定一個(gè)run()方法-然后該方法成為線程的主函數(shù),類似于前面的代碼。
現(xiàn)在,Java程序的一般風(fēng)格是支持繼承的接口。通過使用接口,一個(gè)類在后面仍然能夠繼承(子類化)-如果必要的話(例如,如果該類要在后面作為一個(gè)applet使用的話,就會(huì)發(fā)生這種情況)。

