Java 程序中的多線程(上篇)
程序中使用多線程要比在 C 或 C++ 中容易得多,這是因為 Java 編程語言提供了語言級的支持。本文通過簡單的編程示例來說明 Java 程序中的多線程是多么直觀。讀完本文以后,用戶應(yīng)該能夠編寫簡單的多線程程序。
為什么會排隊等待?
下面的這個簡單的 Java 程序完成四項不相關(guān)的任務(wù)。這樣的程序有單個控制線程,控制在這四個任務(wù)之間線性地移動。此外,因為所需的資源 — 打印機、磁盤、數(shù)據(jù)庫和顯示屏 -- 由于硬件和軟件的限制都有內(nèi)在的潛伏時間,所以每項任務(wù)都包含明顯的等待時間。因此,程序在訪問數(shù)據(jù)庫之前必須等待打印機完成打印文件的任務(wù),等等。如果您正在等待程序的完成,則這是對計算資源和您的時間的一種拙劣使用。改進此程序的一種方法是使它成為多線程的。
四項不相關(guān)的任務(wù)
class myclass { static public void main(String args[]) { print_a_file(); manipulate_another_file(); access_database(); draw_picture_on_screen(); } }
在本例中,每項任務(wù)在開始之前必須等待前一項任務(wù)完成,即使所涉及的任務(wù)毫不相關(guān)也是這樣。但是,在現(xiàn)實生活中,我們經(jīng)常使用多線程模型。我們在處理某些任務(wù)的同時也可以讓孩子、配偶和父母完成別的任務(wù)。例如,我在寫信的同時可能打發(fā)我的兒子去郵局買郵票。用軟件術(shù)語來說,這稱為多個控制(或執(zhí)行)線程。
可以用兩種不同的方法來獲得多個控制線程:
1、多個進程
在大多數(shù)操作系統(tǒng)中都可以創(chuàng)建多個進程。當(dāng)一個程序啟動時,它可以為即將開始的每項任務(wù)創(chuàng)建一個進程,并允許它們同時運行。當(dāng)一個程序因等待網(wǎng)絡(luò)訪問或用戶輸入而被阻塞時,另一個程序還可以運行,這樣就增加了資源利用率。但是,按照這種方式創(chuàng)建每個進程要付出一定的代價:設(shè)置一個進程要占用相當(dāng)一部分處理器時間和內(nèi)存資源。而且,大多數(shù)操作系統(tǒng)不允許進程訪問其他進程的內(nèi)存空間。因此,進程間的通信很不方便,并且也不會將它自己提供給容易的編程模型。
2、線程
線程也稱為輕型進程 (LWP)。因為線程只能在單個進程的作用域內(nèi)活動,所以創(chuàng)建線程比創(chuàng)建進程要廉價得多。這樣,因為線程允許協(xié)作和數(shù)據(jù)交換,并且在計算資源方面非常廉價,所以線程比進程更可取。線程需要操作系統(tǒng)的支持,因此不是所有的機器都提供線程。Java 編程語言,作為相當(dāng)新的一種語言,已將線程支持與語言本身合為一體,這樣就對線程提供了強健的支持。 使用 Java 編程語言實現(xiàn)線程
Java 編程語言使多線程如此簡單有效,以致于某些程序員說它實際上是自然的。盡管在 Java 中使用線程比在其他語言中要容易得多,仍然有一些概念需要掌握。要記住的一件重要的事情是 main() 函數(shù)也是一個線程,并可用來做有用的工作。程序員只有在需要多個線程時才需要創(chuàng)建新的線程。 Thread 類
Thread 類是一個具體的類,即不是抽象類,該類封裝了線程的行為。要創(chuàng)建一個線程,程序員必須創(chuàng)建一個從 Thread 類導(dǎo)出的新類。程序員必須覆蓋 Thread 的 run() 函數(shù)來完成有用的工作。用戶并不直接調(diào)用此函數(shù);而是必須調(diào)用 Thread 的 start() 函數(shù),該函數(shù)再調(diào)用 run()。下面的代碼說明了它的用法:
創(chuàng)建兩個新線程
import java.util.*; class TimePrinter extends Thread { int pauseTime; String name; public TimePrinter(int x, String n) { pauseTime = x; name = n; } public void run() { while(true) { try { System.out.println(name + ":" + new Date(System.currentTimeMillis())); Thread.sleep(pauseTime); } catch(Exception e) { System.out.println(e); } } } static public void main(String args[]) { TimePrinter tp1 = new TimePrinter(1000, "Fast Guy"); tp1.start(); TimePrinter tp2 = new TimePrinter(3000, "Slow Guy"); tp2.start();
} }
在本例中,我們可以看到一個簡單的程序,它按兩個不同的時間間隔(1 秒和 3 秒)在屏幕上顯示當(dāng)前時間。這是通過創(chuàng)建兩個新線程來完成的,包括 main() 共三個線程。但是,因為有時要作為線程運行的類可能已經(jīng)是某個類層次的一部分,所以就不能再按這種機制創(chuàng)建線程。雖然在同一個類中可以實現(xiàn)任意數(shù)量的接口,但 Java 編程語言只允許一個類有一個父類。同時,某些程序員避免從 Thread 類導(dǎo)出,因為它強加了類層次。對于這種情況,就要 runnable 接口。
Runnable 接口
此接口只有一個函數(shù),run(),此函數(shù)必須由實現(xiàn)了此接口的類實現(xiàn)。但是,就運行這個類而論,其語義與前一個示例稍有不同。我們可以用 runnable 接口改寫前一個示例。(不同的部分用黑體表示。)
創(chuàng)建兩個新線程而不強加類層次
import java.util.*; class TimePrinter implements Runnable { int pauseTime; String name; public TimePrinter(int x, String n) { pauseTime = x; name = n; } public void run() { while(true) { try { System.out.println(name + ":" + new Date(System.currentTimeMillis())); Thread.sleep(pauseTime); } catch(Exception e) { System.out.println(e); } } } static public void main(String args[]) { Thread t1 = new Thread(new TimePrinter(1000, "Fast Guy")); t1.start(); Thread t2 = new Thread(new TimePrinter(3000, "Slow Guy")); t2.start();
} }
請注意,當(dāng)使用 runnable 接口時,您不能直接創(chuàng)建所需類的對象并運行它;必須從 Thread 類的一個實例內(nèi)部運行它。許多程序員更喜歡 runnable 接口,因為從 Thread 類繼承會強加類層次。
|