所有重要的操作系統(tǒng)都支持進(jìn)程的概念 —— 獨(dú)立運(yùn)行的程序,在某種程度上相互隔離。
線程有時(shí)稱為 輕量級(jí)進(jìn)程。與進(jìn)程一樣,它們擁有通過程序運(yùn)行的獨(dú)立的并發(fā)路徑,
并且每個(gè)線程都有自己的程序計(jì)數(shù)器,稱為堆棧和本地變量。然而,線程存在于進(jìn)程中,
它們與同一進(jìn)程內(nèi)的其他線程共享內(nèi)存、文件句柄以及每進(jìn)程狀態(tài)。
在 Java 程序中存在很多理由使用線程,并且不管開發(fā)人員知道線程與否,幾乎每個(gè)
Java 應(yīng)用程序都使用線程。許多 J2SE 和 J2EE 工具可以創(chuàng)建線程,如 RMI、Servlet、Enterprise JavaBeans
組件和 Swing GUI 工具包。
AWT 和 Swing
這些 GUI 工具包創(chuàng)建了稱為時(shí)間線程的后臺(tái)線程,將從該線程調(diào)用通過 GUI 組件注冊(cè)的
監(jiān)聽器。因此,實(shí)現(xiàn)這些監(jiān)聽器的類必須是線程安全的。
TimerTask
JDK 1.3 中引入的 TimerTask
工具允許稍后執(zhí)行任務(wù)或計(jì)劃定期執(zhí)行任務(wù)。在 Timer
線程中執(zhí)行 TimerTask
事件,這意味著作為 TimerTask
執(zhí)行的任務(wù)必須是線程安全的。
Servlet 和 JavaServer Page 技術(shù)
Servlet 容器可以創(chuàng)建多個(gè)線程,在多個(gè)線程中同時(shí)調(diào)用給定 servlet,從而進(jìn)行多個(gè)請(qǐng)求。
因此 servlet 類必須是線程安全的。
RMI
遠(yuǎn)程方法調(diào)用(remote method invocation,RMI)工具允許調(diào)用其他 JVM 中運(yùn)行的
操作。實(shí)現(xiàn)遠(yuǎn)程對(duì)象最普遍的方法是擴(kuò)展 UnicastRemoteObject
。例示 UnicastRemoteObject
時(shí),它是通過 RMI 調(diào)度器注冊(cè)的,該調(diào)度器可能創(chuàng)建一個(gè)或
多個(gè)線程,將在這些線程中執(zhí)行遠(yuǎn)程方法。因此,遠(yuǎn)程類必須是線程安全的。
正如所看到的,即使應(yīng)用程序沒有明確創(chuàng)建線程,也會(huì)發(fā)生許多可能會(huì)從其他線程調(diào)用類
的情況。幸運(yùn)的是,java.util.concurrent
中的類可以大大簡(jiǎn)化編寫線程安全類
的任務(wù)。
例子 —— 非線程安全 servlet |
下列 servlet 看起來像無害的留言板 servlet,它保存每個(gè)來訪者的姓名。然而,該
servlet 不是線程安全的,而這個(gè) servlet 應(yīng)該是線程安全的。問題在于它使用
HashSet
存儲(chǔ)來訪者的姓名,HashSet
不是線程安全的類。
當(dāng)我們說這個(gè) servlet 不是線程安全的時(shí),是說它所造成的破壞不僅僅是丟失留言板
輸入。在最壞的情況下,留言板數(shù)據(jù)結(jié)構(gòu)都可能被破壞并且無法恢復(fù)。
public class UnsafeGuestbookServlet extends HttpServlet {
private Set visitorSet = new HashSet();
protected void doGet(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws ServletException, IOException {
String visitorName = httpServletRequest.getParameter("NAME");
if (visitorName != null)
visitorSet.add(visitorName);
}
}
通過將 visitorSet
的定義更改為下列代碼,可以使該類變?yōu)榫€程安全的:
private Set visitorSet = Collections.synchronizedSet(new HashSet());
JDK 1.2 中引入的 Collection 框架是一種表示對(duì)象集合的高度靈活的框架,
它使用
基本接口 List
、Set
和 Map
。通過 JDK 提供每個(gè)集合的多次實(shí)現(xiàn)
(
HashMap
、
Hashtable
、TreeMap
、WeakHashMap
、HashSet
、TreeSet
、
Vector
、ArrayList
、
LinkedList
等等)。其中一些集合已經(jīng)是線程安
全的(
Hashtable
和Vector
),通過同步的封裝工廠
(Collections.synchronizedMap()
、synchronizedList()
和 synchronizedSet()
),其余的集合均可表現(xiàn)為線程安全的。
線程創(chuàng)建 |
線程最普遍的一個(gè)應(yīng)用程序是創(chuàng)建一個(gè)或多個(gè)線程,以執(zhí)行特定類型的任務(wù)。
Timer
類創(chuàng)建線
程來執(zhí)行 TimerTask
對(duì)象,Swing 創(chuàng)建線程來處理 UI 事件。在這兩種情況中,
在單獨(dú)線程
中執(zhí)行的任務(wù)都假定是短期的,這些線程是為了處理大量短期任務(wù)而存在的。
在其中每種情況中,這些線程一般都有非常簡(jiǎn)單的結(jié)構(gòu):
while (true) {
if (no tasks)
wait for a task;
execute the task;
}
通過例示從 Thread
獲得的對(duì)象并調(diào)用 Thread.start()
方法來創(chuàng)建線程。
可以用兩種方法創(chuàng)建線程:通過擴(kuò)展 Thread
和覆蓋 run()
方法,或者通過
實(shí)現(xiàn) Runnable
接口和使用 Thread(Runnable)
構(gòu)造函數(shù):
class WorkerThread extends Thread {
public void run() { /* do work */ }
}
Thread t = new WorkerThread();
t.start();
或者:
Thread t = new Thread(new Runnable() {
public void run() { /* do work */ }
}
t.start();
如何不對(duì)任務(wù)進(jìn)行管理 |
大多數(shù)服務(wù)器應(yīng)用程序(如 Web 服務(wù)器、POP 服務(wù)器、數(shù)據(jù)庫服務(wù)器或文件
服務(wù)器)代表遠(yuǎn)程客戶機(jī)處理請(qǐng)求,這些客戶機(jī)通常使用 socket 連接到服務(wù)器。
對(duì)于每個(gè)請(qǐng)求,通常要進(jìn)行少量處理(獲得該文件的代碼塊,并將其發(fā)送回
socket),但是可能會(huì)有大量(且不受限制)的客戶機(jī)請(qǐng)求服務(wù)。
用于構(gòu)建服務(wù)器應(yīng)用程序的簡(jiǎn)單化模型會(huì)為每個(gè)請(qǐng)求創(chuàng)建新的線程。下列代碼段
實(shí)現(xiàn)簡(jiǎn)單的 Web 服務(wù)器,它接受端口 80 的 socket 連接,并創(chuàng)建新的線程來
處理請(qǐng)求。不幸的是,該代碼不是實(shí)現(xiàn) Web 服務(wù)器的好方法,因?yàn)樵谥刎?fù)載條
件下它將失敗,停止整臺(tái)服務(wù)器.
class UnreliableWebServer {
public static void main(String[] args) {
ServerSocket socket = new ServerSocket(80);
while (true) {
final Socket connection = socket.accept();
Runnable r = new Runnable() {
public void run() {
handleRequest(connection);
}
};
// Don‘t do this!
new Thread(r).start();
}
}
}
當(dāng)服務(wù)器被請(qǐng)求吞沒時(shí),UnreliableWebServer
類不能很好地處理這種情況。
每次有請(qǐng)求時(shí),就會(huì)創(chuàng)建新的類。根據(jù)操作系統(tǒng)和可用內(nèi)存,可以創(chuàng)建的線程數(shù)
是有限的。不幸的是,您通常不知道限制是多少 —— 只有當(dāng)應(yīng)用程序因?yàn)?
OutOfMemoryError
而崩潰時(shí)才發(fā)現(xiàn)。
使用線程池解決問題 |
為任務(wù)創(chuàng)建新的線程并不一定不好,但是如果創(chuàng)建任務(wù)的頻率高,而平均任務(wù)持
續(xù)時(shí)間低,我們可以看到每項(xiàng)任務(wù)創(chuàng)建一個(gè)新的線程將產(chǎn)生性能(如果負(fù)載不可
預(yù)知,還有穩(wěn)定性)問題。 如果不是每項(xiàng)任務(wù)創(chuàng)建一個(gè)新的線程,則服務(wù)器應(yīng)
用程序必須采取一些方法來限制一次可以處理的請(qǐng)求數(shù)。這意味著每次需要啟動(dòng)
新的任務(wù)時(shí),它不能僅調(diào)用下列代碼。
new Thread(runnable).start()
管理一大組小任務(wù)的標(biāo)準(zhǔn)機(jī)制是組合工作隊(duì)列和線程池。工作隊(duì)列就是要處理的
任務(wù)的隊(duì)列,前面描述的 Queue
類完全適合。線程池是線程的集合,每個(gè)線程
都提取公用工作隊(duì)列。當(dāng)一個(gè)工作線程完成任務(wù)處理后,它會(huì)返回隊(duì)列,查看是
否有其他任務(wù)需要處理。如果有,它會(huì)轉(zhuǎn)移到下一個(gè)任務(wù),并開始處理。
Executor 框架 |
java.util.concurrent
包中包含靈活的線程池實(shí)現(xiàn),但是更重要的是,
它包含用于管理實(shí)現(xiàn) Runnable
的任務(wù)的執(zhí)行的整個(gè)框架。該框架稱為
Executor 框架。
Executor
接口相當(dāng)簡(jiǎn)單。它描述將運(yùn)行Runnable
的對(duì)象:
public interface Executor {
void execute(Runnable command);
}
任務(wù)運(yùn)行于哪個(gè)線程不是由該接口指定的,這取決于使用的 Executor
的實(shí)現(xiàn)。
它可以運(yùn)行于后臺(tái)線程,如 Swing 事件線程,或者運(yùn)行于線程池,或者調(diào)用
線程,或者新的線程,它甚至可以運(yùn)行于其他 JVM!通過同步的 Executor
接口提交任務(wù),從任務(wù)執(zhí)行策略中刪除任務(wù)提交。Executor
接口獨(dú)自關(guān)注任
務(wù)提交 —— 這是 Executor
實(shí)現(xiàn)的選擇,確定執(zhí)行策略。這使在部署時(shí)調(diào)整
執(zhí)行策略(隊(duì)列限制、池大小、優(yōu)先級(jí)排列等等)更加容易,更改的代碼最少。
Executor |
java.util.concurrent
包包含多個(gè)Executor
實(shí)現(xiàn),每個(gè)實(shí)現(xiàn)都實(shí)現(xiàn)不
同的執(zhí)行策略。什么是執(zhí)行策略?執(zhí)行策略定義何時(shí)在哪個(gè)線程中運(yùn)行任務(wù),
執(zhí)行任務(wù)可能消耗的資源級(jí)別(線程、內(nèi)存等等),以及如果執(zhí)行程序超載
該怎么辦。 執(zhí)行程序通常通過工廠方法例示,而不是通過構(gòu)造函數(shù)。Executors
類包含用于構(gòu)造許多不同類型的 Executor
實(shí)現(xiàn)的靜態(tài)工廠方法:
Executors.newCachedThreadPool()
創(chuàng)建不限制大小線程池,但是當(dāng)以Executors.newFixedThreadPool(int n)
創(chuàng)建線程池,其重新使用在不Executors.newSingleThreadExecutor()
創(chuàng)建 Executor,其使用在不受class ReliableWebServer {
Executor pool =
Executors.newFixedThreadPool(7);
public static void main(String[] args) {
ServerSocket socket = new ServerSocket(80);
while (true) {
final Socket connection = socket.accept();
Runnable r = new Runnable() {
public void run() {
handleRequest(connection);
}
};
pool.execute(r);
}
}
}
定制 ThreadPoolExecutor
Executors
中的newFixedThreadPool
和newCachedThreadPool
工廠方法返回的
Executor
是類ThreadPoolExecutor
的實(shí)例,是高度可定制的。
通過使用包含 ThreadFactory
變量的工廠方法或構(gòu)造函數(shù)的版本,可以定義池線程的創(chuàng)建。
ThreadFactory
是工廠對(duì)象,其構(gòu)造執(zhí)行程序要使用的新線程。使用定制的線程工廠,創(chuàng)建
的線程可以包含有用的線程名稱,并且這些線程是守護(hù)線程,屬于特定線程組或具有特定優(yōu)先級(jí)。
下面是線程工廠的例子,它創(chuàng)建守護(hù)線程,而不是創(chuàng)建用戶線程:
public class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
}
}
java.util.concurrent
中其他類別的有用的類也是同步工具。這組類相互協(xié)作,控制一個(gè)或多個(gè)線程的執(zhí)
行流。
Semaphore
、CyclicBarrier
、CountdownLatch
和Exchanger
類都是同步工具的例子。
每個(gè)類都有線程可以調(diào)用的方法,方法是否被阻塞取決于正在使用的特定同步
工具的狀態(tài)和規(guī)則。
聯(lián)系客服