細(xì)心!用心!耐心!

          吾非文人,乃市井一俗人也,讀百卷書(shū),跨江河千里,故申城一游; 一兩滴辛酸,三四年學(xué)業(yè),五六點(diǎn)粗墨,七八筆買(mǎi)賣(mài),九十道人情。

          BlogJava 聯(lián)系 聚合 管理
            1 Posts :: 196 Stories :: 10 Comments :: 0 Trackbacks

          JAVA后臺(tái)程序設(shè)計(jì)及UTIL.CONCURRENT包的應(yīng)用

          JAVA后臺(tái)程序設(shè)計(jì)及UTIL.CONCURRENT包的應(yīng)用

          何 恐

          摘要 : 在很多軟件項(xiàng)目中,JAVA語(yǔ)言常常被用來(lái)開(kāi)發(fā)后臺(tái)服務(wù)程序。線(xiàn)程池技術(shù)是提高這類(lèi)程序性能的一個(gè)重要手段。在實(shí)踐中,該技術(shù)已經(jīng)被廣泛的使用。本文首先 對(duì)設(shè)計(jì)后臺(tái)服務(wù)程序通常需要考慮的問(wèn)題進(jìn)行了基本的論述,隨后介紹了JAVA線(xiàn)程池的原理、使用和其他一些相關(guān)問(wèn)題,最后對(duì)功能強(qiáng)大的JAVA開(kāi)放源碼線(xiàn) 程池包util.concurrent 在實(shí)際編程中的應(yīng)用進(jìn)行了詳細(xì)介紹。
          關(guān)鍵字: JAVA;線(xiàn)程池;后臺(tái)服務(wù)程序;util.concurrent


          1 引言
          在軟件項(xiàng)目開(kāi)發(fā)中,許多后臺(tái)服務(wù)程序的處理動(dòng)作流程都具有一個(gè)相同點(diǎn),就是:接受客戶(hù)端發(fā)來(lái)的請(qǐng)求,對(duì)請(qǐng)求進(jìn)行一些相關(guān)的處理,最后將處理結(jié)果返回給客戶(hù) 端。這些請(qǐng)求的來(lái)源和方式可能會(huì)各不相同,但是它們常常都有一個(gè)共同點(diǎn):數(shù)量巨大,處理時(shí)間短。這類(lèi)服務(wù)器在實(shí)際應(yīng)用中具有較大的普遍性,如web服務(wù) 器,短信服務(wù)器,DNS服務(wù)器等等。因此,研究如何提高此類(lèi)后臺(tái)程序的性能,如何保證服務(wù)器的穩(wěn)定性以及安全性都具有重要的實(shí)用價(jià)值。

          2 后臺(tái)服務(wù)程序設(shè)計(jì)
          2.1 關(guān)于設(shè)計(jì)原型
          構(gòu)建服務(wù)器應(yīng)用程序的一個(gè)簡(jiǎn)單的模型是:?jiǎn)?dòng)一個(gè)無(wú)限循環(huán),循環(huán)里放一個(gè)監(jiān)聽(tīng)線(xiàn)程監(jiān)聽(tīng)某個(gè)地址端口。每當(dāng)一個(gè)請(qǐng)求到達(dá)就創(chuàng)建一個(gè)新線(xiàn)程,然后新線(xiàn)程為請(qǐng)求服務(wù),監(jiān)聽(tīng)線(xiàn)程返回繼續(xù)監(jiān)聽(tīng)。
          簡(jiǎn)單舉例如下:
          import java.net.*;
          public class MyServer extends Thread{
          public void run(){
          try{
          ServerSocket server=null;
          Socket clientconnection=null;
          server = new ServerSocket(8008);//監(jiān)聽(tīng)某地址端口對(duì)
          while(true){進(jìn)入無(wú)限循環(huán)
          clientconnection =server.accept();//收取請(qǐng)求
          new ServeRequest(clientconnection).start();//啟動(dòng)一個(gè)新服務(wù)線(xiàn)程進(jìn)行服務(wù)
          ……
          }
          }catch(Exception e){
          System.err.println("Unable to start serve listen:"+e.getMessage());
          e.printStackTrace();
          }
          }
          }
          實(shí)際上,這只是個(gè)簡(jiǎn)單的原型,如果試圖部署以這種方式運(yùn)行的服務(wù)器應(yīng)用程序,那么這種方法的嚴(yán)重不足就很明顯。
          首先,為每個(gè)請(qǐng)求創(chuàng)建一個(gè)新線(xiàn)程的開(kāi)銷(xiāo)很大,為每個(gè)請(qǐng)求創(chuàng)建新線(xiàn)程的服務(wù)器在創(chuàng)建和銷(xiāo)毀線(xiàn)程上花費(fèi)的時(shí)間和消耗的系統(tǒng)資源, 往往有時(shí)候要比花在處理實(shí)際的用戶(hù)請(qǐng)求的時(shí)間和資源更多。在Java中更是如此,虛擬機(jī)將試圖跟蹤每一個(gè)對(duì)象,以便能夠在對(duì)象銷(xiāo)毀后進(jìn)行垃圾回收。所以提 高服務(wù)程序效率的一個(gè)手段就是盡可能減少創(chuàng)建和銷(xiāo)毀對(duì)象的次數(shù)。這樣綜合看來(lái),系統(tǒng)的性能瓶頸就在于線(xiàn)程的創(chuàng)建開(kāi)銷(xiāo)。
          其次,除了創(chuàng)建和銷(xiāo)毀線(xiàn)程的開(kāi)銷(xiāo)之外,活動(dòng)的線(xiàn)程也消耗系統(tǒng)資源。在一個(gè) JVM 里創(chuàng)建太多的線(xiàn)程可能會(huì)導(dǎo)致系統(tǒng)由于過(guò)度消耗內(nèi)存而用完內(nèi)存或“切換過(guò)度”。為了防止資源不足,服務(wù)器應(yīng)用程序需要一些辦法來(lái)限制任何給定時(shí)刻運(yùn)行的處理 線(xiàn)程數(shù)目,以防止服務(wù)器被“壓死”的情況發(fā)生。所以在設(shè)計(jì)后臺(tái)程序的時(shí)候,一般需要提前根據(jù)服務(wù)器的內(nèi)存、CPU等硬件情況設(shè)定一個(gè)線(xiàn)程數(shù)量的上限值。
          如果創(chuàng)建和銷(xiāo)毀線(xiàn)程的時(shí)間相對(duì)于服務(wù)時(shí)間占用的比例較大,那末假設(shè)在一個(gè)較短的時(shí)間內(nèi)有成千上萬(wàn)的請(qǐng)求到達(dá),想象一下,服務(wù)器的時(shí)間和資源將會(huì)大量的花在 創(chuàng)建和銷(xiāo)毀線(xiàn)程上,而真正用于處理請(qǐng)求的時(shí)間卻相對(duì)較少,這種情況下,服務(wù)器性能瓶頸就在于創(chuàng)建和銷(xiāo)毀線(xiàn)程的時(shí)間。按照這個(gè)模型寫(xiě)一個(gè)簡(jiǎn)單的程序測(cè)試一下 即可看出,由于篇幅關(guān)系,此處略。如果把(服務(wù)時(shí)間/創(chuàng)建和銷(xiāo)毀線(xiàn)程的時(shí)間)作為衡量服務(wù)器性能的一個(gè)參數(shù),那末這個(gè)比值越大,服務(wù)器的性能就越高。
          應(yīng)此,解決此類(lèi)問(wèn)題的實(shí)質(zhì)就是盡量減少創(chuàng)建和銷(xiāo)毀線(xiàn)程的時(shí)間,把服務(wù)器的資源盡可能多地用到處理請(qǐng)求上來(lái),從而發(fā)揮多線(xiàn)程的優(yōu)點(diǎn)(并發(fā)),避免多線(xiàn)程的缺點(diǎn)(創(chuàng)建和銷(xiāo)毀的時(shí)空開(kāi)銷(xiāo))。
          線(xiàn)程池為線(xiàn)程生命周期開(kāi)銷(xiāo)問(wèn)題和資源不足問(wèn)題提供了解決方案。通過(guò)對(duì)多個(gè)任務(wù)重用線(xiàn)程,線(xiàn)程創(chuàng)建的開(kāi)銷(xiāo)被分?jǐn)偟搅硕鄠€(gè)任務(wù)上。其好處是,因?yàn)樵谡?qǐng)求到達(dá)時(shí) 線(xiàn)程已經(jīng)存在,所以無(wú)意中也消除了線(xiàn)程創(chuàng)建所帶來(lái)的延遲。這樣,就可以立即為請(qǐng)求服務(wù),使應(yīng)用程序響應(yīng)更快。而且,通過(guò)適當(dāng)?shù)卣{(diào)整線(xiàn)程池中的線(xiàn)程數(shù)目,也 就是當(dāng)請(qǐng)求的數(shù)目超過(guò)某個(gè)閾值時(shí),就強(qiáng)制其它任何新到的請(qǐng)求一直等待,直到獲得一個(gè)線(xiàn)程來(lái)處理為止,從而可以防止資源不足。

          3    JAVA線(xiàn)程池原理
          3.1 原理以及實(shí)現(xiàn)
          在實(shí)踐中,關(guān)于線(xiàn)程池的實(shí)現(xiàn)常常有不同的方法,但是它們的基本思路大都是相似的:服務(wù)器預(yù)先存放一定數(shù)目的“熱”的線(xiàn)程,并發(fā)程序需要使用線(xiàn)程的時(shí)候,從 服務(wù)器取用一條已經(jīng)創(chuàng)建好的線(xiàn)程(如果線(xiàn)程池為空則等待),使用該線(xiàn)程對(duì)請(qǐng)求服務(wù),使用結(jié)束后,該線(xiàn)程并不刪除,而是返回線(xiàn)程池中,以備復(fù)用,這樣可以避 免對(duì)每一個(gè)請(qǐng)求都生成和刪除線(xiàn)程的昂貴操作。
          一個(gè)比較簡(jiǎn)單的線(xiàn)程池至少應(yīng)包含線(xiàn)程池管理器、工作線(xiàn)程、任務(wù)隊(duì)列、任務(wù)接口等部分。其中線(xiàn)程池管理器(ThreadPool Manager)的作用是創(chuàng)建、銷(xiāo)毀并管理線(xiàn)程池,將工作線(xiàn)程放入線(xiàn)程池中;工作線(xiàn)程是一個(gè)可以循環(huán)執(zhí)行任務(wù)的線(xiàn)程,在沒(méi)有任務(wù)時(shí)進(jìn)行等待;任務(wù)隊(duì)列的作 用是提供一種緩沖機(jī)制,將沒(méi)有處理的任務(wù)放在任務(wù)隊(duì)列中;任務(wù)接口是每個(gè)任務(wù)必須實(shí)現(xiàn)的接口,主要用來(lái)規(guī)定任務(wù)的入口、任務(wù)執(zhí)行完后的收尾工作、任務(wù)的執(zhí) 行狀態(tài)等,工作線(xiàn)程通過(guò)該接口調(diào)度任務(wù)的執(zhí)行。下面的代碼實(shí)現(xiàn)了創(chuàng)建一個(gè)線(xiàn)程池:
          public class ThreadPool
          {
          private Stack threadpool = new Stack();
          private int poolSize;
          private int currSize=0;
          public void setSize(int n)
          {
          poolSize = n;
          }
          public void run()
          {
          for(int i=0;i

          (發(fā)帖時(shí)間:2003-11-30 11:55:56)
          --- 岑心 J

          回復(fù)(1):

          4.2    框架與結(jié)構(gòu)
          下面讓我們來(lái)看看util.concurrent的框架結(jié)構(gòu)。關(guān)于這個(gè)工具包概述的e文原版鏈接地址是http: //gee.cs.oswego.edu/dl/cpjslides/util.pdf。該工具包主要包括三大部分:同步、通道和線(xiàn)程池執(zhí)行器。第一部分 主要是用來(lái)定制鎖,資源管理,其他的同步用途;通道則主要是為緩沖和隊(duì)列服務(wù)的;線(xiàn)程池執(zhí)行器則提供了一組完善的復(fù)雜的線(xiàn)程池實(shí)現(xiàn)。
          --主要的結(jié)構(gòu)如下圖所示

          4.2.1 Sync
          acquire/release協(xié)議的主要接口
          - 用來(lái)定制鎖,資源管理,其他的同步用途
          - 高層抽象接口
          - 沒(méi)有區(qū)分不同的加鎖用法

          實(shí)現(xiàn)
          -Mutex, ReentrantLock, Latch, CountDown,Semaphore, WaiterPreferenceSemaphore, FIFOSemaphore, PrioritySemaphore
          還有,有幾個(gè)簡(jiǎn)單的實(shí)現(xiàn),例如ObservableSync, LayeredSync

          舉例:如果我們要在程序中獲得一獨(dú)占鎖,可以用如下簡(jiǎn)單方式:
          try {
          lock.acquire();
          try {
          action();
          }
          finally {
          lock.release();
          }
          }catch(Exception e){
          }

          程序中,使用lock對(duì)象的acquire()方法獲得一獨(dú)占鎖,然后執(zhí)行您的操作,鎖用完后,使用release()方法釋放之即可。呵呵,簡(jiǎn)單吧,想 想看,如果您親自撰寫(xiě)?yīng)氄兼i,大概會(huì)考慮到哪些問(wèn)題?如果關(guān)鍵的鎖得不到怎末辦?用起來(lái)是不是會(huì)復(fù)雜很多?而現(xiàn)在,以往的很多細(xì)節(jié)和特殊異常情況在這里都 無(wú)需多考慮,您盡可以把精力花在解決您的應(yīng)用問(wèn)題上去。

          4.2.2 通道(Channel)
          為緩沖,隊(duì)列等服務(wù)的主接口

          具體實(shí)現(xiàn)
          LinkedQueue, BoundedLinkedQueue,BoundedBuffer, BoundedPriorityQueue, SynchronousChannel, Slot

          通道例子
          class Service { // ...
          final Channel msgQ = new LinkedQueue();
          public void serve() throws InterruptedException {
          String status = doService();
          msgQ.put(status);
          }
          public Service() { // start background thread
          Runnable logger = new Runnable() {
          public void run() {
          try {
          for(;;)
          System.out.println(msqQ.take());
          }
          catch(InterruptedException ie) {} }
          };
          new Thread(logger).start();
          }
          }
          在后臺(tái)服務(wù)器中,緩沖和隊(duì)列都是最常用到的。試想,如果對(duì)所有遠(yuǎn)端的請(qǐng)求不排個(gè)隊(duì)列,讓它們一擁而上的去爭(zhēng)奪cpu、內(nèi)存、資源,那服務(wù)器瞬間不當(dāng)?shù)舨殴?。而在這里,成熟的隊(duì)列和緩沖實(shí)現(xiàn)已經(jīng)提供,您只需要對(duì)其進(jìn)行正確初始化并使用即可,大大縮短了開(kāi)發(fā)時(shí)間。

          4.2.3執(zhí)行器(Executor)
          Executor是這里最重要、也是我們往往最終寫(xiě)程序要用到的,下面重點(diǎn)對(duì)其進(jìn)行介紹。
          類(lèi)似線(xiàn)程的類(lèi)的主接口
          - 線(xiàn)程池
          - 輕量級(jí)運(yùn)行框架
          - 可以定制調(diào)度算法

          只需要支持execute(Runnable r)
          - 同Thread.start類(lèi)似

          實(shí)現(xiàn)
          - PooledExecutor, ThreadedExecutor, QueuedExecutor, FJTaskRunnerGroup

          PooledExecutor(線(xiàn)程池執(zhí)行器)是個(gè)最常用到的類(lèi),以它為例:
          可修改得屬性如下:
          - 任務(wù)隊(duì)列的類(lèi)型
          - 最大線(xiàn)程數(shù)
          - 最小線(xiàn)程數(shù)
          - 預(yù)熱(預(yù)分配)和立即(分配)線(xiàn)程
          - 保持活躍直到工作線(xiàn)程結(jié)束
          -- 以后如果需要可能被一個(gè)新的代替
          - 飽和(Saturation)協(xié)議
          -- 阻塞,丟棄,生產(chǎn)者運(yùn)行,等等

          可不要小看上面這數(shù)條屬性,對(duì)這些屬性的設(shè)置完全可以等同于您自己撰寫(xiě)的線(xiàn)程池的成百上千行代碼。下面以筆者撰寫(xiě)過(guò)得一個(gè)GIS服務(wù)器為例:
          該GIS服務(wù)器是一個(gè)典型的“請(qǐng)求-服務(wù)”類(lèi)型的服務(wù)器,遵循后端程序設(shè)計(jì)的一般框架。首先對(duì)所有的請(qǐng)求按照先來(lái)先服務(wù)排入一個(gè)請(qǐng)求隊(duì)列,如果瞬間到達(dá)的 請(qǐng)求超過(guò)了請(qǐng)求隊(duì)列的容量,則將溢出的請(qǐng)求轉(zhuǎn)移至一個(gè)臨時(shí)隊(duì)列。如果臨時(shí)隊(duì)列也排滿(mǎn)了,則對(duì)以后達(dá)到的請(qǐng)求給予一個(gè)“服務(wù)器忙”的提示后將其簡(jiǎn)單拋棄。這 個(gè)就夠忙活一陣的了。
          然后,結(jié)合鏈表結(jié)構(gòu)實(shí)現(xiàn)一個(gè)線(xiàn)程池,給池一個(gè)初始容量。如果該池滿(mǎn),以x2的策略將池的容量動(dòng)態(tài)增加一倍,依此類(lèi)推,直到總線(xiàn)程數(shù)服務(wù)達(dá)到系統(tǒng)能力上限, 之后線(xiàn)程池容量不在增加,所有請(qǐng)求將等待一個(gè)空余的返回線(xiàn)程。每從池中得到一個(gè)線(xiàn)程,該線(xiàn)程就開(kāi)始最請(qǐng)求進(jìn)行GIS信息的服務(wù),如取坐標(biāo)、取地圖,等等。 服務(wù)完成后,該線(xiàn)程返回線(xiàn)程池繼續(xù)為請(qǐng)求隊(duì)列離地后續(xù)請(qǐng)求服務(wù),周而復(fù)始。當(dāng)時(shí)用矢量鏈表來(lái)暫存請(qǐng)求,用wait()、 notify() 和 synchronized等原語(yǔ)結(jié)合矢量鏈表實(shí)現(xiàn)線(xiàn)程池,總共約600行程序,而且在運(yùn)行時(shí)間較長(zhǎng)的情況下服務(wù)器不穩(wěn)定,線(xiàn)程池被取用的線(xiàn)程有異常消失的 情況發(fā)生。而使用util.concurrent相關(guān)類(lèi)之后,僅用了幾十行程序就完成了相同的工作而且服務(wù)器運(yùn)行穩(wěn)定,線(xiàn)程池沒(méi)有丟失線(xiàn)程的情況發(fā)生。由 此可見(jiàn)util.concurrent包極大的提高了開(kāi)發(fā)效率,為項(xiàng)目節(jié)省了大量的時(shí)間。
          使用PooledExecutor例子
          import java.net.*;
          /**
          *
          Title:
          *?
          Description: 負(fù)責(zé)初始化線(xiàn)程池以及啟動(dòng)服務(wù)器
          *
          Copyright: Copyright (c) 2003
          *
          Company:
          * @author not attributable
          * @version 1.0
          */
          public class MainServer {
          //初始化常量
          public static final int MAX_CLIENT=100; //系統(tǒng)最大同時(shí)服務(wù)客戶(hù)數(shù)
          //初始化線(xiàn)程池
          public static final PooledExecutor pool =
          new PooledExecutor(new BoundedBuffer(10), MAX_CLIENT); //chanel容量為10,
          //在這里為線(xiàn)程池初始化了一個(gè)
          //長(zhǎng)度為10的任務(wù)緩沖隊(duì)列。

          public MainServer() {
          //設(shè)置線(xiàn)程池運(yùn)行參數(shù)
          pool.setMinimumPoolSize(5); //設(shè)置線(xiàn)程池初始容量為5個(gè)線(xiàn)程
          pool.discardOldestWhenBlocked();//對(duì)于超出隊(duì)列的請(qǐng)求,使用了拋棄策略。
          pool.createThreads(2); //在線(xiàn)程池啟動(dòng)的時(shí)候,初始化了具有一定生命周期的2個(gè)“熱”線(xiàn)程
          }

          public static void main(String[] args) {
          MainServer MainServer1 = new MainServer();
          new HTTPListener().start();//啟動(dòng)服務(wù)器監(jiān)聽(tīng)和處理線(xiàn)程
          new manageServer().start();//啟動(dòng)管理線(xiàn)程
          }
          }



          類(lèi)HTTPListener
          import java.net.*;
          /**
          *
          Title:
          *
          Description: 負(fù)責(zé)監(jiān)聽(tīng)端口以及將任務(wù)交給線(xiàn)程池處理
          *
          Copyright: Copyright (c) 2003
          * Company:
          * @author not attributable
          * @version 1.0
          */
          public class HTTPListener extends Thread{
          public HTTPListener() {
          }
          public void run(){
          try{
          ServerSocket server=null;
          Socket clientconnection=null;
          server = new ServerSocket(8008);//服務(wù)套接字監(jiān)聽(tīng)某地址端口對(duì)
          while(true){//無(wú)限循環(huán)
          clientconnection =server.accept();
          System.out.println("Client connected in!");
          //使用線(xiàn)程池啟動(dòng)服務(wù)
          MainServer.pool.execute(new HTTPRequest(clientconnection));//如果收到一個(gè)請(qǐng)求,則從線(xiàn)程池中取一個(gè)線(xiàn)程進(jìn)行服務(wù),任務(wù)完成后,該線(xiàn)程自動(dòng)返還線(xiàn)程池
          }
          }catch(Exception e){
          System.err.println("Unable to start serve listen:"+e.getMessage());
          e.printStackTrace();
          }
          }
          }

          關(guān)于util.concurrent工具包就有選擇的介紹到這,更詳細(xì)的信息可以閱讀這些java源代碼的API文檔。Doug Lea是個(gè)很具有“open”精神的作者,他將util.concurrent工具包的java源代碼全部公布出來(lái),有興趣的讀者可以下載這些源代碼并細(xì) 細(xì)品味。

          5    結(jié)束語(yǔ)
          以上內(nèi)容介紹了線(xiàn)程池基本原理以及設(shè)計(jì)后臺(tái)服務(wù)程序應(yīng)考慮到的問(wèn)題,并結(jié)合實(shí)例詳細(xì)介紹了重要的多線(xiàn)程開(kāi)發(fā)工具包util.concurrent的構(gòu)架和使用。結(jié)合使用已有完善的開(kāi)發(fā)包,后端服務(wù)程序的開(kāi)發(fā)周期將大大縮短,同時(shí)程序性能也有了保障。



          參考文獻(xiàn)
          [1] Chad Darby,etc. 《Beginning Java Networking》. 電子工業(yè)出版社. 2002年3月.
          [2] util.concurrent 說(shuō)明文件 http://gee.cs.oswego.edu/dl/cpjslides/util.pdf
          [3] 幸勇.線(xiàn)程池的介紹及簡(jiǎn)單實(shí)現(xiàn).http://www-900.ibm.com/developerWorks/cn/java/l-threadPool/index.shtml
          2002年8月
          [4]BrianGoetz. 我的線(xiàn)程到哪里去了http://www-900.cn.ibm.com/developerworks/cn/java/j-jtp0924/index.shtml

          posted on 2007-01-31 13:52 張金鵬 閱讀(828) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): 多線(xiàn)程編程
          主站蜘蛛池模板: 肥乡县| 阿城市| 离岛区| 林口县| 循化| 昌宁县| 彝良县| 鄂尔多斯市| 定兴县| 淳安县| 布拖县| 筠连县| 黄平县| 越西县| 甘孜县| 荆州市| 北安市| 神池县| 申扎县| 莱西市| 兴隆县| 京山县| 丰县| 罗定市| 太原市| 电白县| 绥滨县| 临高县| 文化| 黑河市| 浦北县| 黄平县| 南开区| 扎赉特旗| 姚安县| 金沙县| 雅安市| 习水县| 洱源县| 隆安县| 崇仁县|