程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 100行Java代碼構建一個線程池

100行Java代碼構建一個線程池

編輯:關於JAVA

本示例程序由三個類構成,第一個是TestThreadPool類,它是一個測試程序,用來模擬客戶端的請求,當你運行它時,系統首先會顯示線程池的初始化信息,然後提示你從鍵盤上輸入字符串,並按下回車鍵,這時你會發現屏幕上顯示信息,告訴你某個線程正在處理你的請求,如果你快速地輸入一行行字符串,那麼你會發現線程池中不斷有線程被喚醒,來處理你的請求,在本例中,我創建了一個擁有10個線程的線程池,如果線程池中沒有可用線程了,系統會提示你相應的警告信息,但如果你稍等片刻,那你會發現屏幕上會陸陸續續提示有線程進入了睡眠狀態,這時你又可以發送新的請求了。

第二個類是ThreadPoolManager類,顧名思義,它是一個用於管理線程池的類,它的主要職責是初始化線程池,並為客戶端的請求分配不同的線程來進行處理,如果線程池滿了,它會對你發出警告信息。

最後一個類是SimpleThread類,它是Thread類的一個子類,它才真正對客戶端的請求進行處理,SimpleThread在示例程序初始化時都處於睡眠狀態,但如果它接受到了ThreadPoolManager類發過來的調度信息,則會將自己喚醒,並對請求進行處理。

首先我們來看一下TestThreadPool類的源碼:

//TestThreadPool.Java

1 import Java.io.*;

2

3

4 public class TestThreadPool

5 {

6 public static void main(String[] args)

7 {

8 try{

9 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

10 String s;

11 ThreadPoolManager manager = new ThreadPoolManager(10);

12 while((s = br.readLine()) != null)

13 {

14 manager.process(s);

15 }

16 }catch(IOException e){}

17 }

18 }

 由於此測試程序用到了輸入輸入類,因此第1行導入了Java的基本IO處理包,在第11行中,我們創建了一個名為manager的類,它給ThreadPoolManager類的構造函數傳遞了一個值為10的參數,告訴ThreadPoolManager類:我要一個有10個線程的池,給我創建一個吧!第12行至15行是一個無限循環,它用來等待用戶的鍵入,並將鍵入的字符串保存在s變量中,並調用ThreadPoolManager類的process方法來將這個請求進行處理。

下面我們再進一步跟蹤到ThreadPoolManager類中去,以下是它的源代碼:

//ThreadPoolManager.Java

1 import Java.util.*;

2

3

4 class ThreadPoolManager

5 {

6

7 private int maxThread;

8 public Vector vector;

9 public void setMaxThread(int threadCount)

10 {

11 maxThread = threadCount;

12 }

13

14 public ThreadPoolManager(int threadCount)

15 {

16 setMaxThread(threadCount);

17 System.out.println("Starting thread pool...");

18 vector = new Vector();

19 for(int i = 1; i <= 10; i++)

20 {

21 SimpleThread thread = new SimpleThread(i);

22 vector.addElement(thread);

23 thread.start();

24 }

25 }

26

27 public void process(String argument)

28 {

29 int i;

30 for(i = 0; i < vector.size(); i++)

31 {

32 SimpleThread currentThread = (SimpleThread)vector.elementAt(i);

33 if(!currentThread.isRunning())

34 {

35 System.out.println("Thread "+ (i+1) +" is processing:" +

argument);

36 currentThread.setArgument(argument);

37 currentThread.setRunning(true);

38 return;

39 }

40 }

41 if(i == vector.size())

42 {

43 System.out.println("pool is full, try in another time.");

44 }

45 }

46 }//end of class ThreadPoolManager

我們先關注一下這個類的構造函數,然後再看它的process()方法。第16-24行是它的構造函數,首先它給ThreadPoolManager類的成員變量maxThread賦值,maxThread表示用於控制線程池中最大線程的數量。第18行初始化一個數組vector,它用來存放所有的SimpleThread類,這時候就充分體現了JAVA語言的優越性與藝術性:如果你用C語言的話,至少要寫100行以上的代碼來完成vector的功能,而且C語言數組只能容納類型統一的基本數據類型,無法容納對象。好了,閒話少說,第19-24行的循環完成這樣一個功能:先創建一個新的SimpleThread類,然後將它放入vector中去,最後用thread.start()來啟動這個線程,為什麼要用start()方法來啟動線程呢?因為這是Java語言中所規定的,如果你不用的話,那這些線程將永遠得不到激活,從而導致本示例程序根本無法運行。

下面我們再來看一下process()方法,第30-40行的循環依次從vector數組中選取SimpleThread線程,並檢查它是否處於激活狀態(所謂激活狀態是指此線程是否正在處理客戶端的請求),如果處於激活狀態的話,那繼續查找vector數組的下一項,如果vector數組中所有的線程都處於激活狀態的話,那它會打印出一條信息,提示用戶稍候再試。相反如果找到了一個睡眠線程的話,那第35-38行會對此進行處理,它先告訴客戶端是哪一個線程來處理這個請求,然後將客戶端的請求,即字符串argument轉發給SimpleThread類的setArgument()方法進行處理,並調用SimpleThread類的setRunning()方法來喚醒當前線程,來對客戶端請求進行處理。

可能你還對setRunning()方法是怎樣喚醒線程的有些不明白,那我們現在就進入最後一個類:SimpleThread類,它的源代碼如下:

//SimpleThread.Java

1 class SimpleThread extends Thread

2 {

3 private boolean runningFlag;

4 private String argument;

5 public boolean isRunning()

6 {

7 return runningFlag;

8 }

9 public synchronized void setRunning(boolean flag)

10 {

11 runningFlag = flag;

12 if(flag)

13 this.notify();

14 }

15

16 public String getArgument()

17 {

18 return this.argument;

19 }

20 public void setArgument(String string)

21 {

22 argument = string;

23 }

24

25 public SimpleThread(int threadNumber)

26 {

27 runningFlag = false;

28 System.out.println("thread " + threadNumber + "started.");

29 }

30

31 public synchronized void run()

32 {

33 try{

34 while(true)

35 {

36 if(!runningFlag)

37 {

38 this.wait();

39 }

40 else

41 {

42 System.out.println("processing " + getArgument() + "... done.");

43 sleep(5000);

44 System.out.println("Thread is sleeping...");

45 setRunning(false);

46 }

47 }

48 } catch(InterruptedException e){

49 System.out.println("Interrupt");

50 }

51 }//end of run()

52 }//end of class SimpleThread

如果你對JAVA的線程編程有些不太明白的話,那我先在這裡簡單地講解一下,JAVA有一個名為Thread的類,如果你要創建一個線程,則必須要從Thread類中繼承,並且還要實現Thread類的run()接口,要激活一個線程,必須調用它的start()方法,start()方法會自動調用run()接口,因此用戶必須在run()接口中寫入自己的應用處理邏輯。那麼我們怎麼來控制線程的睡眠與喚醒呢?其實很簡單,Java語言為所有的對象都內置了wait()和notify()方法,當一個線程調用wait()方法時,則線程進入睡眠狀態,就像停在了當前代碼上了,也不會繼續執行它以下的代碼了,當調用notify()方法時,則會從調用wait()方法的那行代碼繼續執行以下的代碼,這個過程有點像編譯器中的斷點調試的概念。以本程序為例,第38行調用了wait()方法,則這個線程就像凝固了一樣停在了38行上了,如果我們在第13行進行一個notify()調用的話,那線程會從第38行上喚醒,繼續從第39行開始執行以下的代碼了。

通過以上的講述,我們現在就不難理解SimpleThread類了,第9-14行通過設置一個標志runningFlag激活當前線程,第25-29行是SimpleThread類的構造函數,它用來告訴客戶端啟動的是第幾號進程。第31-50行則是我實現的run()接口,它實際上是一個無限循環,在循環中首先判斷一下標志runningFlag,如果沒有runningFlag為false的話,那線程處理睡眠狀態,否則第42-45行會進行真正的處理:先打印用戶鍵入的字符串,然後睡眠5秒鐘,為什麼要睡眠5秒鐘呢?如果你不加上這句代碼的話,由於計算機處理速度遠遠超過你的鍵盤輸入速度,因此你看到的總是第1號線程來處理你的請求,從而達不到演示效果。最後第45行調用setRunning()方法又將線程置於睡眠狀態,等待新請求的到來。

最後還有一點要注意的是,如果你在一個方法中調用了wait()和notify()函數,那你一定要將此方法置為同步的,即synchronized,否則在編譯時會報錯,並得到一個莫名其妙的消息:“current thread not owner”(當前線程不是擁有者)。

至此為止,我們完整地實現了一個線程池,當然,這個線程池只是簡單地將客戶端輸入的字符串打印到了屏幕上,而沒有做任何處理,對於一個真正的企業級運用,本例還是遠遠不夠的,例如錯誤處理、線程的動態調整、性能優化、臨界區的處理、客戶端報文的定義等等都是值得考慮的問題,但本文的目的僅僅只是讓你了解線程池的概念以及它的簡單實現,如果你想成為這方面的高手,本文是遠遠不夠的,你應該參考一些更多的資料來深入地了解它。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved