溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

100行Java代碼構(gòu)建一個(gè)線(xiàn)程池

發(fā)布時(shí)間:2020-08-11 21:48:59 來(lái)源:ITPUB博客 閱讀:108 作者:pingyuan 欄目:編程語(yǔ)言
100行Java代碼構(gòu)建一個(gè)線(xiàn)程池[@more@]在現(xiàn)代的操作系統(tǒng)中,有一個(gè)很重要的概念――線(xiàn)程,幾乎所有目前流行的操作系統(tǒng)都支持線(xiàn)程,線(xiàn)程來(lái)源于操作系統(tǒng)中進(jìn)程的概念,進(jìn)程有自己的虛擬地址空間以及正文段、數(shù)據(jù)段及堆棧,而且各自占有不同的系統(tǒng)資源(例如文件、環(huán)境變量等等)。與此不同,線(xiàn)程不能單獨(dú)存在,它依附于進(jìn)程,只能由進(jìn)程派生。如果一個(gè)進(jìn)程派生出了兩個(gè)線(xiàn)程,那這兩個(gè)線(xiàn)程共享此進(jìn)程的全局變量和代碼段,但每個(gè)線(xiàn)程各擁有各自的堆棧,因此它們擁有各自的局部變量,線(xiàn)程在UNIX系統(tǒng)中還被進(jìn)一步分為用戶(hù)級(jí)線(xiàn)程(由進(jìn)程自已來(lái)管理)和系統(tǒng)級(jí)線(xiàn)程(由操作系統(tǒng)的調(diào)度程序來(lái)管理)。

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

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

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

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

  第二個(gè)類(lèi)是ThreadPoolManager類(lèi),顧名思義,它是一個(gè)用于管理線(xiàn)程池的類(lèi),它的主要職責(zé)是初始化線(xiàn)程池,并為客戶(hù)端的請(qǐng)求分配不同的線(xiàn)程來(lái)進(jìn)行處理,如果線(xiàn)程池滿(mǎn)了,它會(huì)對(duì)你發(fā)出警告信息。

  最后一個(gè)類(lèi)是SimpleThread類(lèi),它是Thread類(lèi)的一個(gè)子類(lèi),它才真正對(duì)客戶(hù)端的請(qǐng)求進(jìn)行處理,SimpleThread在示例程序初始化時(shí)都處于睡眠狀態(tài),但如果它接受到了ThreadPoolManager類(lèi)發(fā)過(guò)來(lái)的調(diào)度信息,則會(huì)將自己?jiǎn)拘?,并?duì)請(qǐng)求進(jìn)行處理。


  首先我們來(lái)看一下TestThreadPool類(lèi)的源碼:



//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 }



  由于此測(cè)試程序用到了輸入輸入類(lèi),因此第1行導(dǎo)入了JAVA的基本IO處理包,在第11行中,我們創(chuàng)建了一個(gè)名為manager的類(lèi),它給ThreadPoolManager類(lèi)的構(gòu)造函數(shù)傳遞了一個(gè)值為10的參數(shù),告訴ThreadPoolManager類(lèi):我要一個(gè)有10個(gè)線(xiàn)程的池,給我創(chuàng)建一個(gè)吧!第12行至15行是一個(gè)無(wú)限循環(huán),它用來(lái)等待用戶(hù)的鍵入,并將鍵入的字符串保存在s變量中,并調(diào)用ThreadPoolManager類(lèi)的process方法來(lái)將這個(gè)請(qǐng)求進(jìn)行處理。

  下面我們?cè)龠M(jìn)一步跟蹤到ThreadPoolManager類(lèi)中去,以下是它的源代碼:



//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è)類(lèi)的構(gòu)造函數(shù),然后再看它的process()方法。第16-24行是它的構(gòu)造函數(shù),首先它給ThreadPoolManager類(lèi)的成員變量maxThread賦值,maxThread表示用于控制線(xiàn)程池中最大線(xiàn)程的數(shù)量。第18行初始化一個(gè)數(shù)組vector,它用來(lái)存放所有的SimpleThread類(lèi),這時(shí)候就充分體現(xiàn)了JAVA語(yǔ)言的優(yōu)越性與藝術(shù)性:如果你用C語(yǔ)言的話(huà),至少要寫(xiě)100行以上的代碼來(lái)完成vector的功能,而且C語(yǔ)言數(shù)組只能容納類(lèi)型統(tǒng)一的基本數(shù)據(jù)類(lèi)型,無(wú)法容納對(duì)象。好了,閑話(huà)少說(shuō),第19-24行的循環(huán)完成這樣一個(gè)功能:先創(chuàng)建一個(gè)新的SimpleThread類(lèi),然后將它放入vector中去,最后用thread.start()來(lái)啟動(dòng)這個(gè)線(xiàn)程,為什么要用start()方法來(lái)啟動(dòng)線(xiàn)程呢?因?yàn)檫@是JAVA語(yǔ)言中所規(guī)定的,如果你不用的話(huà),那這些線(xiàn)程將永遠(yuǎn)得不到激活,從而導(dǎo)致本示例程序根本無(wú)法運(yùn)行。

  下面我們?cè)賮?lái)看一下process()方法,第30-40行的循環(huán)依次從vector數(shù)組中選取SimpleThread線(xiàn)程,并檢查它是否處于激活狀態(tài)(所謂激活狀態(tài)是指此線(xiàn)程是否正在處理客戶(hù)端的請(qǐng)求),如果處于激活狀態(tài)的話(huà),那繼續(xù)查找vector數(shù)組的下一項(xiàng),如果vector數(shù)組中所有的線(xiàn)程都處于激活狀態(tài)的話(huà),那它會(huì)打印出一條信息,提示用戶(hù)稍候再試。相反如果找到了一個(gè)睡眠線(xiàn)程的話(huà),那第35-38行會(huì)對(duì)此進(jìn)行處理,它先告訴客戶(hù)端是哪一個(gè)線(xiàn)程來(lái)處理這個(gè)請(qǐng)求,然后將客戶(hù)端的請(qǐng)求,即字符串a(chǎn)rgument轉(zhuǎn)發(fā)給SimpleThread類(lèi)的setArgument()方法進(jìn)行處理,并調(diào)用SimpleThread類(lèi)的setRunning()方法來(lái)喚醒當(dāng)前線(xiàn)程,來(lái)對(duì)客戶(hù)端請(qǐng)求進(jìn)行處理。

  可能你還對(duì)setRunning()方法是怎樣喚醒線(xiàn)程的有些不明白,那我們現(xiàn)在就進(jìn)入最后一個(gè)類(lèi):SimpleThread類(lèi),它的源代碼如下:

//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

  如果你對(duì)JAVA的線(xiàn)程編程有些不太明白的話(huà),那我先在這里簡(jiǎn)單地講解一下,JAVA有一個(gè)名為T(mén)hread的類(lèi),如果你要?jiǎng)?chuàng)建一個(gè)線(xiàn)程,則必須要從Thread類(lèi)中繼承,并且還要實(shí)現(xiàn)Thread類(lèi)的run()接口,要激活一個(gè)線(xiàn)程,必須調(diào)用它的start()方法,start()方法會(huì)自動(dòng)調(diào)用run()接口,因此用戶(hù)必須在run()接口中寫(xiě)入自己的應(yīng)用處理邏輯。那么我們?cè)趺磥?lái)控制線(xiàn)程的睡眠與喚醒呢?其實(shí)很簡(jiǎn)單,JAVA語(yǔ)言為所有的對(duì)象都內(nèi)置了wait()和notify()方法,當(dāng)一個(gè)線(xiàn)程調(diào)用wait()方法時(shí),則線(xiàn)程進(jìn)入睡眠狀態(tài),就像停在了當(dāng)前代碼上了,也不會(huì)繼續(xù)執(zhí)行它以下的代碼了,當(dāng)調(diào)用notify()方法時(shí),則會(huì)從調(diào)用wait()方法的那行代碼繼續(xù)執(zhí)行以下的代碼,這個(gè)過(guò)程有點(diǎn)像編譯器中的斷點(diǎn)調(diào)試的概念。以本程序?yàn)槔?8行調(diào)用了wait()方法,則這個(gè)線(xiàn)程就像凝固了一樣停在了38行上了,如果我們?cè)诘?3行進(jìn)行一個(gè)notify()調(diào)用的話(huà),那線(xiàn)程會(huì)從第38行上喚醒,繼續(xù)從第39行開(kāi)始執(zhí)行以下的代碼了。

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

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

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

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI