閱讀提要:在引入Java多線程技術後,幾乎所有應用程序的開發在性能上都得到瞭很大的改進。本文將通過探討超線程技術以及新出現的多核心Intel處理器技術來分析這些線程技術是怎樣成為Java編程的一個標準部分的。
|
|
一、Java環境下的多線程技術
構建線程化的應用程序往往會對程序帶來重要的性能影響。例如,請考慮這樣一個程序,它從磁盤讀取大量數據並且在把它們寫到屏幕之前處理這些數據(例如一個DVD播放器)。在一個傳統的單線程程序(今天所使用的大多數客戶端程序)上,一次隻有一個任務執行,每一個這些活動分別作為一個序列的不同階段發生。隻有在一塊已定義大小的數據讀取完成時才能進行數據處理。因此,能處理數據的程序邏輯直到磁盤讀操作完成後才得到執行。這將導致非常差的性能問題。
在一個多線程程序中,可以分配一個線程來讀取數據,讓另一個線程來處理數據,而讓第三個線程把數據輸送到圖形卡上去。這三個線程可以並行運行;這樣以來,在磁盤讀取數據的同時仍然可以處理數據,從而提高瞭整體程序的性能。許多大量的示例程序都可以被設計來同時做兩件事情以進一步提高性能。Java虛擬機(JVM)本身就是基於此原因廣泛使用瞭多線程技術。
本文將討論創建多線程Java代碼以及一些進行並行程序設計的最好練習;另外還介紹瞭對開發者極為有用的一些工具和資源。篇幅所限,不可能全面論述這些問題,所以我想隻是重點提一下極重要的地方並提供給你相應的參考信息。
二、線程化Java代碼
所有的程序都至少使用一個線程。在C/C++和Java中,這是指用對main()的調用而啟動的那個線程。另外線程的創建需要若幹步驟:創建一個新線程,然後指定給它某種工作。一旦工作做完,該線程將自動被JVM所殺死。
Java提供兩個方法來創建線程並且指定給它們工作。第一種方法是子類化Java的Thread類(在java.lang包中),然後用該線程的工作函數重載run()方法。下面是這種方法的一個示例:
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()); } } |
這個類子類化Thread並且提供它自己的run()方法。上面代碼中的函數運行一個循環來打印傳送過來的字符串到屏幕上,然後等待一個隨機的時間數目。在循環十次後,該函數打印”DONE!”,然後退出-並由它殺死這個線程。下面是創建線程的主函數:
public class TwoThreadsDemo { public static void main (String[] args) { new SimpleThread(“Do it!”).start(); new SimpleThread(“Definitely not!”).start(); } } |
註意該代碼極為簡單:函數開始,給定一個名字(它是該線程將要打印輸出的字符串)並且調用start()。然後,start()將調用run()方法。程序的結果如下所示:
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! |
正如你所看到的,這兩個線程的輸出結果糾合到一起。在一個單線程程序中,所有的”Do it!”命令將一起打印,後面跟著輸出”Definitely not!”。
這個程序的不同運行將產生不同的結果。這種不確定性來源於兩個方面:在循環中有一個隨機的暫停;更為重要的是,因為線程執行時間沒法保證。這是一個關鍵的原則。JVM將根據它自己的時間表運行這些進程(虛擬機一般支持盡可能快地運行這些線程,但是沒法保證何時運行一個給定線程)。對於每個線程可以使一個優先級與之相關聯以確保關鍵線程被JVM處理在次要的線程之前。
啟動一個線程的第二種方法是使用一個實現Runnable接口的類-這個接口也定義在java.lang中。這個Runnable接口指定一個run()方法-然後該方法成為線程的主函數,類似於前面的代碼。
現在,Java程序的一般風格是支持繼承的接口。通過使用接口,一個類在後面仍然能夠繼承(子類化)-如果必要的話(例如,如果該類要在後面作為一個applet使用的話,就會發生這種情況)。
三、線程的含義
在采用多線程技術增強性能的同時,它也增加瞭程序內部運行的復雜性。這種復雜性主要是由線程之間的交互引起的。熟悉這些問題是很重要的,因為隨著越來越多的核心芯片加入到Intel處理器中,要使用的線程數目也將相應地增長。
|