Posted on 2007-11-09 01:38
dybjsun 閱讀(197)
評論(0) 編輯 收藏 所屬分類:
多線程主題
前面談了多線程應(yīng)用程序能極大地改善用戶相應(yīng)。例如對于一個(gè)Web應(yīng)用程序,每當(dāng)一個(gè)用戶請求服務(wù)器連接時(shí),服務(wù)器就可以啟動一個(gè)新線程為用戶服務(wù)。
然而,創(chuàng)建和銷毀線程本身就有一定的開銷,內(nèi)存開銷就不可忽略,垃圾收集器還必須負(fù)擔(dān)更多的工作。因此,線程池就是為了避免頻繁創(chuàng)建和銷毀線程。
每當(dāng)服務(wù)器接受了一個(gè)新的請求后,服務(wù)器就從線程池中挑選一個(gè)等待的線程并執(zhí)行請求處理。處理完畢后,線程并不結(jié)束,而是轉(zhuǎn)為阻塞狀態(tài)再次被放入線程池中。這樣就避免了頻繁創(chuàng)建和銷毀線程。
Worker Pattern實(shí)現(xiàn)了類似線程池的功能。首先定義Task接口:
package com.crackj2ee.thread;
public interface Task {
void execute();
}
線程將負(fù)責(zé)執(zhí)行execute()方法。注意到任務(wù)是由子類通過實(shí)現(xiàn)execute()方法實(shí)現(xiàn)的,線程本身并不知道自己執(zhí)行的任務(wù)。它只負(fù)責(zé)運(yùn)行一個(gè)耗時(shí)的execute()方法。
具體任務(wù)由子類實(shí)現(xiàn),我們定義了一個(gè)CalculateTask和一個(gè)TimerTask:
// CalculateTask.java
package com.crackj2ee.thread;
public class CalculateTask implements Task {
private static int count = 0;
private int num = count;
public CalculateTask() {
count++;
}
public void execute() {
System.out.println("[CalculateTask " + num + "] start...");
try {
Thread.sleep(3000);
}
catch(InterruptedException ie) {}
System.out.println("[CalculateTask " + num + "] done.");
}
}
// TimerTask.java
package com.crackj2ee.thread;
public class TimerTask implements Task {
private static int count = 0;
private int num = count;
public TimerTask() {
count++;
}
public void execute() {
System.out.println("[TimerTask " + num + "] start...");
try {
Thread.sleep(2000);
}
catch(InterruptedException ie) {}
System.out.println("[TimerTask " + num + "] done.");
}
}
以上任務(wù)均簡單的sleep若干秒。
TaskQueue實(shí)現(xiàn)了一個(gè)隊(duì)列,客戶端可以將請求放入隊(duì)列,服務(wù)器線程可以從隊(duì)列中取出任務(wù):
package com.crackj2ee.thread;
import java.util.*;
public class TaskQueue {
private List queue = new LinkedList();
public synchronized Task getTask() {
while(queue.size()==0) {
try {
this.wait();
}
catch(InterruptedException ie) {
return null;
}
}
return (Task)queue.remove(0);
}
public synchronized void putTask(Task task) {
queue.add(task);
this.notifyAll();
}
}
終于到了真正的WorkerThread,這是真正執(zhí)行任務(wù)的服務(wù)器線程:
package com.crackj2ee.thread;
public class WorkerThread extends Thread {
private static int count = 0;
private boolean busy = false;
private boolean stop = false;
private TaskQueue queue;
public WorkerThread(ThreadGroup group, TaskQueue queue) {
super(group, "worker-" + count);
count++;
this.queue = queue;
}
public void shutdown() {
stop = true;
this.interrupt();
try {
this.join();
}
catch(InterruptedException ie) {}
}
public boolean isIdle() {
return !busy;
}
public void run() {
System.out.println(getName() + " start.");
while(!stop) {
Task task = queue.getTask();
if(task!=null) {
busy = true;
task.execute();
busy = false;
}
}
System.out.println(getName() + " end.");
}
}
前面已經(jīng)講過,queue.getTask()是一個(gè)阻塞方法,服務(wù)器線程可能在此wait()一段時(shí)間。此外,WorkerThread還有一個(gè)shutdown方法,用于安全結(jié)束線程。
最后是ThreadPool,負(fù)責(zé)管理所有的服務(wù)器線程,還可以動態(tài)增加和減少線程數(shù):
package com.crackj2ee.thread;
import java.util.*;
public class ThreadPool extends ThreadGroup {
private List threads = new LinkedList();
private TaskQueue queue;
public ThreadPool(TaskQueue queue) {
super("Thread-Pool");
this.queue = queue;
}
public synchronized void addWorkerThread() {
Thread t = new WorkerThread(this, queue);
threads.add(t);
t.start();
}
public synchronized void removeWorkerThread() {
if(threads.size()>0) {
WorkerThread t = (WorkerThread)threads.remove(0);
t.shutdown();
}
}
public synchronized void currentStatus() {
System.out.println("-----------------------------------------------");
System.out.println("Thread count = " + threads.size());
Iterator it = threads.iterator();
while(it.hasNext()) {
WorkerThread t = (WorkerThread)it.next();
System.out.println(t.getName() + ": " + (t.isIdle() ? "idle" : "busy"));
}
System.out.println("-----------------------------------------------");
}
}
currentStatus()方法是為了方便調(diào)試,打印出所有線程的當(dāng)前狀態(tài)。
最后,Main負(fù)責(zé)完成main()方法:
package com.crackj2ee.thread;
public class Main {
public static void main(String[] args) {
TaskQueue queue = new TaskQueue();
ThreadPool pool = new ThreadPool(queue);
for(int i=0; i<10; i++) {
queue.putTask(new CalculateTask());
queue.putTask(new TimerTask());
}
pool.addWorkerThread();
pool.addWorkerThread();
doSleep(8000);
pool.currentStatus();
pool.addWorkerThread();
pool.addWorkerThread();
pool.addWorkerThread();
pool.addWorkerThread();
pool.addWorkerThread();
doSleep(5000);
pool.currentStatus();
}
private static void doSleep(long ms) {
try {
Thread.sleep(ms);
}
catch(InterruptedException ie) {}
}
}
main()一開始放入了20個(gè)Task,然后動態(tài)添加了一些服務(wù)線程,并定期打印線程狀態(tài),運(yùn)行結(jié)果如下:
worker-0 start.
[CalculateTask 0] start...
worker-1 start.
[TimerTask 0] start...
[TimerTask 0] done.
[CalculateTask 1] start...
[CalculateTask 0] done.
[TimerTask 1] start...
[CalculateTask 1] done.
[CalculateTask 2] start...
[TimerTask 1] done.
[TimerTask 2] start...
[TimerTask 2] done.
[CalculateTask 3] start...
-----------------------------------------------
Thread count = 2
worker-0: busy
worker-1: busy
-----------------------------------------------
[CalculateTask 2] done.
[TimerTask 3] start...
worker-2 start.
[CalculateTask 4] start...
worker-3 start.
[TimerTask 4] start...
worker-4 start.
[CalculateTask 5] start...
worker-5 start.
[TimerTask 5] start...
worker-6 start.
[CalculateTask 6] start...
[CalculateTask 3] done.
[TimerTask 6] start...
[TimerTask 3] done.
[CalculateTask 7] start...
[TimerTask 4] done.
[TimerTask 7] start...
[TimerTask 5] done.
[CalculateTask 8] start...
[CalculateTask 4] done.
[TimerTask 8] start...
[CalculateTask 5] done.
[CalculateTask 9] start...
[CalculateTask 6] done.
[TimerTask 9] start...
[TimerTask 6] done.
[TimerTask 7] done.
-----------------------------------------------
Thread count = 7
worker-0: idle
worker-1: busy
worker-2: busy
worker-3: idle
worker-4: busy
worker-5: busy
worker-6: busy
-----------------------------------------------
[CalculateTask 7] done.
[CalculateTask 8] done.
[TimerTask 8] done.
[TimerTask 9] done.
[CalculateTask 9] done.
仔細(xì)觀察:一開始只有兩個(gè)服務(wù)器線程,因此線程狀態(tài)都是忙,后來線程數(shù)增多,7個(gè)線程中的兩個(gè)狀態(tài)變成idle,說明處于wait()狀態(tài)。
思考:本例的線程調(diào)度算法其實(shí)根本沒有,因?yàn)檫@個(gè)應(yīng)用是圍繞TaskQueue設(shè)計(jì)的,不是以Thread Pool為中心設(shè)計(jì)的。因此,Task調(diào)度取決于TaskQueue的getTask()方法,你可以改進(jìn)這個(gè)方法,例如使用優(yōu)先隊(duì)列,使優(yōu)先級高的任務(wù)先被執(zhí)行。
如果所有的服務(wù)器線程都處于busy狀態(tài),則說明任務(wù)繁忙,TaskQueue的隊(duì)列越來越長,最終會導(dǎo)致服務(wù)器內(nèi)存耗盡。因此,可以限制TaskQueue的等待任務(wù)數(shù),超過最大長度就拒絕處理。許多Web服務(wù)器在用戶請求繁忙時(shí)就會拒絕用戶:HTTP 503 SERVICE UNAVAILABLE