在 Java 程序中使用多線程要比在 C 或 C++ 中容易得多
這是因為 Java 編程語言提供了語言級的支持
本文通過簡單的編程示例來說明 Java 程序中的多線程是多麼直觀
讀完本文以後
用戶應該能夠編寫簡單的多線程程序
為什麼會排隊等待? 下面的這個簡單的 Java 程序完成四項不相關的任務
這樣的程序有單個控制線程
控制在這四個任務之間線性地移動
此外
因為所需的資源 — 打印機
磁盤
數據庫和顯示屏
由於硬件和軟件的限制都有內在的潛伏時間
所以每項任務都包含明顯的等待時間
因此
程序在訪問數據庫之前必須等待打印機完成打印文件的任務
等等
如果您正在等待程序的完成
則這是對計算資源和您的時間的一種拙劣使用
改進此程序的一種方法是使它成為多線程的
四項不相關的任務
class myclass {
static public void main(String args[]) {
print_a_file();
manipulate_another_file();
access_database();
draw_picture_on_screen();
}
}
在本例中
每項任務在開始之前必須等待前一項任務完成
即使所涉及的任務毫不相關也是這樣
但是
在現實生活中
我們經常使用多線程模型
我們在處理某些任務的同時也可以讓孩子
配偶和父母完成別的任務
例如
我在寫信的同時可能打發我的兒子去郵局買郵票
用軟件術語來說
這稱為多個控制(或執行)線程
可以用兩種不同的方法來獲得多個控制線程
多個進程
在大多數操作系統中都可以創建多個進程
當一個程序啟動時
它可以為即將開始的每項任務創建一個進程
並允許它們同時運行
當一個程序因等待網絡訪問或用戶輸入而被阻塞時
另一個程序還可以運行
這樣就增加了資源利用率
但是
按照這種方式創建每個進程要付出一定的代價
設置一個進程要占用相當一部分處理器時間和內存資源
而且
大多數操作系統不允許進程訪問其他進程的內存空間
因此
進程間的通信很不方便
並且也不會將它自己提供給容易的編程模型
線程
線程也稱為輕型進程 (LWP)
因為線程只能在單個進程的作用域內活動
所以創建線程比創建進程要廉價得多
這樣
因為線程允許協作和數據交換
並且在計算資源方面非常廉價
所以線程比進程更可取
線程需要操作系統的支持
因此不是所有的機器都提供線程
Java 編程語言
作為相當新的一種語言
已將線程支持與語言本身合為一體
這樣就對線程提供了強健的支持
使用 Java 編程語言實現線程 Java編程語言使多線程如此簡單有效
以致於某些程序員說它實際上是自然的
盡管在 Java 中使用線程比在其他語言中要容易得多
仍然有一些概念需要掌握
要記住的一件重要的事情是 main() 函數也是一個線程
並可用來做有用的工作
程序員只有在需要多個線程時才需要創建新的線程
Thread 類
Thread 類是一個具體的類
即不是抽象類
該類封裝了線程的行為
要創建一個線程
程序員必須創建一個從 Thread 類導出的新類
程序員必須覆蓋 Thread 的 run() 函數來完成有用的工作
用戶並不直接調用此函數
而是必須調用 Thread 的 start() 函數
該函數再調用 run()
下面的代碼說明了它的用法
創建兩個新線程
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 tp
= new TimePrinter(
Fast Guy
);
tp
start();
TimePrinter tp
= new TimePrinter(
Slow Guy
);
tp
start();
}
}
在本例中
我們可以看到一個簡單的程序
它按兩個不同的時間間隔(
秒和
秒)在屏幕上顯示當前時間
這是通過創建兩個新線程來完成的
包括 main() 共三個線程
但是
因為有時要作為線程運行的類可能已經是某個類層次的一部分
所以就不能再按這種機制創建線程
雖然在同一個類中可以實現任意數量的接口
但 Java 編程語言只允許一個類有一個父類
同時
某些程序員避免從 Thread 類導出
因為它強加了類層次
對於這種情況
就要 runnable 接口
Runnable 接口 此接口只有一個函數
run()
此函數必須由實現了此接口的類實現
但是
就運行這個類而論
其語義與前一個示例稍有不同
我們可以用 runnable 接口改寫前一個示例
(不同的部分用黑體表示
)
創建兩個新線程而不強加類層次
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 t
= new Thread(new TimePrinter(
Fast Guy
));
t
start();
Thread t
= new Thread(new TimePrinter(
Slow Guy
));
t
start();
}
}
請注意
當使用 runnable 接口時
您不能直接創建所需類的對象並運行它
必須從 Thread 類的一個實例內部運行它
許多程序員更喜歡 runnable 接口
因為從 Thread 類繼承會強加類層次
synchronized 關鍵字 到目前為止
我們看到的示例都只是以非常簡單的方式來利用線程
只有最小的數據流
而且不會出現兩個線程訪問同一個對象的情況
但是
在大多數有用的程序中
線程之間通常有信息流
試考慮一個金融應用程序
它有一個 Account 對象
如下例中所示
一個銀行中的多項活動
public class Account {
String holderName;
float amount;
public Account(String name
float amt) {
holderName = name;
amount = amt;
}
public void deposit(float amt) {
amount += amt;
}
public void withdraw(float amt) {
amount
= amt;
}
public float checkBalance() {
return amount;
}
}
在此代碼樣例中潛伏著一個錯誤
如果此類用於單線程應用程序
不會有任何問題
但是
在多線程應用程序的情況中
不同的線程就有可能同時訪問同一個 Account 對象
比如說一個聯合帳戶的所有者在不同的 ATM 上同時進行訪問
在這種情況下
存入和支出就可能以這樣的方式發生
一個事務被另一個事務覆蓋
這種情況將是災難性的
但是
Java 編程語言提供了一種簡單的機制來防止發生這種覆蓋
每個對象在運行時都有一個關聯的鎖
這個鎖可通過為方法添加關鍵字 synchronized 來獲得
這樣
修訂過的 Account 對象(如下所示)將不會遭受像數據損壞這樣的錯誤
對一個銀行中的多項活動進行同步處理
public class Account {
String holderName;
float amount;
public Account(String name
float amt) {
holderName = name;
amount = amt;
}
public synchronized void deposit(float amt) {
amount += amt;
}
public synchronized void withdraw(float amt) {
amount
= amt;
}
public float checkBalance() {
return amount;
}
}
deposit() 和 withdraw() 函數都需要這個鎖來進行操作
所以當一個函數運行時
另一個函數就被阻塞
請注意
checkBalance() 未作更改
它嚴格是一個讀函數
因為 checkBalance() 未作同步處理
所以任何其他方法都不會阻塞它
它也不會阻塞任何其他方法
不管那些方法是否進行了同步處理
Java 編程語言中的高級多線程支持 線程組
線程是被個別創建的
但可以將它們歸類到線程組中
以便於調試和監視
只能在創建線程的同時將它與一個線程組相關聯
在使用大量線程的程序中
使用線程組組織線程可能很有幫助
可以將它們看作是計算機上的目錄和文件結構
線程間發信
當線程在繼續執行前需要等待一個條件時
僅有 synchronized 關鍵字是不夠的
雖然 synchronized 關鍵字阻止並發更新一個對象
但它沒有實現線程間發信
Object 類為此提供了三個函數
wait()
notify() 和 notifyAll()
以全球氣候預測程序為例
這些程序通過將地球分為許多單元
在每個循環中
每個單元的計算都是隔離進行的
直到這些值趨於穩定
然後相鄰單元之間就會交換一些數據
所以
從本質上講
在每個循環中各個線程都必須等待所有線程完成各自的任務以後才能進入下一個循環
這個模型稱為 屏蔽同步
下例說明了這個模型
<
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27599.html