<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    我的漫漫程序之旅

    專注于JavaWeb開發(fā)
    隨筆 - 39, 文章 - 310, 評論 - 411, 引用 - 0
    數(shù)據(jù)加載中……

    用Java代碼構(gòu)建一個線程池

    在現(xiàn)代的操作系統(tǒng)中,有一個很重要的概念――線程,幾乎所有目前流行的操作系統(tǒng)都支持線程,線程來源于操作系統(tǒng)中進程的概念,進程有自己的虛擬地址空間以及正文段、數(shù)據(jù)段及堆棧,而且各自占有不同的系統(tǒng)資源(例如文件、環(huán)境變量等等)。與此不同,線程不能單獨存在,它依附于進程,只能由進程派生。如果一個進程派生出了兩個線程,那這兩個線程共享此進程的全局變量和代碼段,但每個線程各擁有各自的堆棧,因此它們擁有各自的局部變量,線程在UNIX系統(tǒng)中還被進一步分為用戶級線程(由進程自已來管理)和系統(tǒng)級線程(由操作系統(tǒng)的調(diào)度程序來管理)。

      既然有了進程,為什么還要提出線程的概念呢?因為與創(chuàng)建一個新的進程相比,創(chuàng)建一個線程將會耗費小得多的系統(tǒng)資源,對于一些小型的應(yīng)用,可能感覺不到這點,但對于那些并發(fā)進程數(shù)特別多的應(yīng)用,使用線程會比使用進程獲得更好的性能,從而降低操作系統(tǒng)的負擔(dān)。另外,線程共享創(chuàng)建它的進程的全局變量,因此線程間的通訊編程會更將簡單,完全可以拋棄傳統(tǒng)的進程間通訊的IPC編程,而采用共享全局變量來進行線程間通訊。

      有了上面這個概念,我們下面就進入正題,來看一下線程池究竟是怎么一回事?其實線程池的原理很簡單,類似于操作系統(tǒng)中的緩沖區(qū)的概念,它的流程如下:先啟動若干數(shù)量的線程,并讓這些線程都處于睡眠狀態(tài),當(dāng)客戶端有一個新請求時,就會喚醒線程池中的某一個睡眠線程,讓它來處理客戶端的這個請求,當(dāng)處理完這個請求后,線程又處于睡眠狀態(tài)。可能你也許會問:為什么要搞得這么麻煩,如果每當(dāng)客戶端有新的請求時,我就創(chuàng)建一個新的線程不就完了?這也許是個不錯的方法,因為它能使得你編寫代碼相對容易一些,但你卻忽略了一個重要的問題――性能!就拿我所在的單位來說,我的單位是一個省級數(shù)據(jù)大集中的銀行網(wǎng)絡(luò)中心,高峰期每秒的客戶端請求并發(fā)數(shù)超過100,如果為每個客戶端請求創(chuàng)建一個新線程的話,那耗費的CPU時間和內(nèi)存將是驚人的,如果采用一個擁有200個線程的線程池,那將會節(jié)約大量的的系統(tǒng)資源,使得更多的CPU時間和內(nèi)存用來處理實際的商業(yè)應(yīng)用,而不是頻繁的線程創(chuàng)建與銷毀。

      既然一切都明白了,那我們就開始著手實現(xiàn)一個真正的線程池吧,線程編程可以有多種語言來實現(xiàn),例如C、C++、java等等,但不同的操作系統(tǒng)提供不同的線程API接口,為了讓你能更明白線程池的原理而避免陷入煩瑣的API調(diào)用之中,我采用了JAVA語言來實現(xiàn)它,由于JAVA語言是一種跨平臺的語言,因此你不必為使用不同的操作系統(tǒng)而無法編譯運行本程序而苦惱,只要你安裝了JDK1.2以上的版本,都能正確地編譯運行本程序。另外JAVA語言本身就內(nèi)置了線程對象,而且JAVA語言是完全面像對象的,因此能夠讓你更清晰地了解線程池的原理,如果你注意看一下本文的標(biāo)題,你會發(fā)現(xiàn)整個示例程序的代碼只有大約100行。

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

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

      最后一個類是SimpleThread類,它是Thread類的一個子類,它才真正對客戶端的請求進行處理,SimpleThread在示例程序初始化時都處于睡眠狀態(tài),但如果它接受到了ThreadPoolManager類發(fā)過來的調(diào)度信息,則會將自己喚醒,并對請求進行處理。
       首先我們來看一下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行導(dǎo)入了JAVA的基本IO處理包,在第11行中,我們創(chuàng)建了一個名為manager的類,它給ThreadPoolManager類的構(gòu)造函數(shù)傳遞了一個值為10的參數(shù),告訴ThreadPoolManager類:我要一個有10個線程的池,給我創(chuàng)建一個吧!第12行至15行是一個無限循環(huán),它用來等待用戶的鍵入,并將鍵入的字符串保存在s變量中,并調(diào)用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 


      我們先關(guān)注一下這個類的構(gòu)造函數(shù),然后再看它的process()方法。第16-24行是它的構(gòu)造函數(shù),首先它給ThreadPoolManager類的成員變量maxThread賦值,maxThread表示用于控制線程池中最大線程的數(shù)量。第18行初始化一個數(shù)組vector,它用來存放所有的SimpleThread類,這時候就充分體現(xiàn)了JAVA語言的優(yōu)越性與藝術(shù)性:如果你用C語言的話,至少要寫100行以上的代碼來完成vector的功能,而且C語言數(shù)組只能容納類型統(tǒng)一的基本數(shù)據(jù)類型,無法容納對象。好了,閑話少說,第19-24行的循環(huán)完成這樣一個功能:先創(chuàng)建一個新的SimpleThread類,然后將它放入vector中去,最后用thread.start()來啟動這個線程,為什么要用start()方法來啟動線程呢?因為這是JAVA語言中所規(guī)定的,如果你不用的話,那這些線程將永遠得不到激活,從而導(dǎo)致本示例程序根本無法運行。
       下面我們再來看一下process()方法,第30-40行的循環(huán)依次從vector數(shù)組中選取SimpleThread線程,并檢查它是否處于激活狀態(tài)(所謂激活狀態(tài)是指此線程是否正在處理客戶端的請求),如果處于激活狀態(tài)的話,那繼續(xù)查找vector數(shù)組的下一項,如果vector數(shù)組中所有的線程都處于激活狀態(tài)的話,那它會打印出一條信息,提示用戶稍候再試。相反如果找到了一個睡眠線程的話,那第35-38行會對此進行處理,它先告訴客戶端是哪一個線程來處理這個請求,然后將客戶端的請求,即字符串a(chǎn)rgument轉(zhuǎn)發(fā)給SimpleThread類的setArgument()方法進行處理,并調(diào)用SimpleThread類的setRunning()方法來喚醒當(dāng)前線程,來對客戶端請求進行處理。

      可能你還對setRunning()方法是怎樣喚醒線程的有些不明白,那我們現(xiàn)在就進入最后一個類: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的類,如果你要創(chuàng)建一個線程,則必須要從Thread類中繼承,并且還要實現(xiàn)Thread類的run()接口,要激活一個線程,必須調(diào)用它的start()方法,start()方法會自動調(diào)用run()接口,因此用戶必須在run()接口中寫入自己的應(yīng)用處理邏輯。那么我們怎么來控制線程的睡眠與喚醒呢?其實很簡單,JAVA語言為所有的對象都內(nèi)置了wait()和notify()方法,當(dāng)一個線程調(diào)用wait()方法時,則線程進入睡眠狀態(tài),就像停在了當(dāng)前代碼上了,也不會繼續(xù)執(zhí)行它以下的代碼了,當(dāng)調(diào)用notify()方法時,則會從調(diào)用wait()方法的那行代碼繼續(xù)執(zhí)行以下的代碼,這個過程有點像編譯器中的斷點調(diào)試的概念。以本程序為例,第38行調(diào)用了wait()方法,則這個線程就像凝固了一樣停在了38行上了,如果我們在第13行進行一個notify()調(diào)用的話,那線程會從第38行上喚醒,繼續(xù)從第39行開始執(zhí)行以下的代碼了。

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

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

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


    posted on 2007-12-22 15:53 々上善若水々 閱讀(1603) 評論(0)  編輯  收藏 所屬分類: J2SE

    主站蜘蛛池模板: 成人黄软件网18免费下载成人黄18免费视频 | 操美女视频免费网站| 亚洲国产精彩中文乱码AV| 国产特黄一级一片免费| 亚洲国产精品13p| 免费看黄网站在线看| 亚洲第一永久AV网站久久精品男人的天堂AV| 亚洲中文字幕乱码AV波多JI| 国产一卡二卡3卡四卡免费| 亚洲一卡2卡3卡4卡国产网站| **aaaaa毛片免费| 亚洲校园春色另类激情| 免费A级毛片无码免费视| 国产亚洲精品bv在线观看| 我要看WWW免费看插插视频| 亚洲精华国产精华精华液好用| 国产大片51精品免费观看| 黄色片网站在线免费观看| 精品亚洲一区二区三区在线观看| 青青操免费在线观看| 亚洲精品第五页中文字幕| 成人免费看片又大又黄| 爱爱帝国亚洲一区二区三区| 又粗又大又长又爽免费视频 | 亚洲精品美女视频| 岛国片在线免费观看| 一级毛片大全免费播放下载| 亚洲中文字幕久久精品无码喷水| 一级毛片免费毛片一级毛片免费 | 亚洲一级特黄大片无码毛片| 久草福利资源网站免费| 亚洲乱码在线观看| 亚洲男人av香蕉爽爽爽爽| 中文字幕视频免费| 亚洲aⅴ无码专区在线观看春色 | 麻豆国产精品免费视频| 国产亚洲综合精品一区二区三区| 亚洲国产精品无码专区在线观看| 久久福利资源网站免费看| 一区二区3区免费视频| 亚洲国产综合精品|