在開發Socket項目的時候,如果是開發一個自己玩玩,當然不用考慮效率、安全性等問題,可是如果是一個企業級的,你就不得不關注這幾點。本系列文章將我們實驗室裡的Socket程式變成企業級的應用。
NIO編程肯定是一個很好的解決方案,不過這部分留在以後討論。今天我想說說如何讓你的阻塞的Scoket程式高效、安全的跑起來。
一開始,大家會編出一個單線程的Scoket程式,然後我們發現這個程式根本不能夠串連多個用戶端,於是我們引入“多線程”,使我們的程式能夠同時處理多個用戶端。
我相信,到現在為止如果沒有深入研究過Socket編程,大家一般還是停留在“一客戶一線程”的初級模式。如果是個位元的客戶,當然你不會發現什麼明顯的效能問題。但是如果你的客戶串連數量達到百位級,我靠,你的CPU就關顧著在各個線程間切換,你的記憶體似乎也有些吃不消了(每個線程都有自己獨立的記憶體),更多的系統資源的消耗,更多的線程上下文轉換,更複雜的線程管理(OS有一套自己的機制),將拖垮你的application。再加上多用戶端嘗試並發串連,及時響應用戶端的串連將變得像癩蛤蟆追求天鵝一樣不給力,因為線程的建立將佔用伺服器大量的CPU周期。
從單線程到多線程我們沒法解決的問題,必然有新的英雄站出來解決,他就是“線程池”。
Java代碼
- import java.io.IOException;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- /**
- * ThreadPool示範
- * @project : socket
- * @author 賈懂凱 @ netjava
- * @date 2010-12-14 下午12:24:40
- * @since jdk1.6.0_10
- */
- public
class TCPServerPool {
- public
static
void main(String args[]) throws IOException{
- //ensure the parameter is right!
- if(args.length!=2){
- throw
new IllegalArgumentException("Parameter(s):<port> <ThreadSize>");
- }
- int server_port=Integer.parseInt(args[0]);
- int threadpool_size=Integer.parseInt(args[1]);
- //create a server socket to accept client connection requests
- final ServerSocket serSock=new ServerSocket(server_port);
- final Logger logger=Logger.getLogger("thredPoolLog");
- //spawn a fixed number of threds to service clients
- for(int i=0;i<threadpool_size;i++){
- Runnable run=new Runnable(){
- public
void run() {
- while(true){
- try {
- Socket clientSock=serSock.accept();
- /**
- * 把它交給一個獨立的handler處理
- * 你可以將handler定義為一個獨立的線程(注意效能)
- * 或者定義為一個靜態方法(注意並發的同步問題)。
- */
- } catch (IOException e) {
- logger.log(Level.WARNING,"Client accept failed",e);
- }
- }
- }
- };
- Thread t=new Thread(run,"Thread-"+i);
- t.start();
- logger.info("create and start a thread named "+t.getName());
- }
- }
- }
import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.util.logging.Level;import java.util.logging.Logger;/** * ThreadPool示範 * @project : socket * @author 賈懂凱 @ netjava * @date 2010-12-14 下午12:24:40 * @since jdk1.6.0_10 */public class TCPServerPool { public static void main(String args[]) throws IOException{ //ensure the parameter is right! if(args.length!=2){ throw new IllegalArgumentException("Parameter(s):<port> <ThreadSize>"); } int server_port=Integer.parseInt(args[0]); int threadpool_size=Integer.parseInt(args[1]); //create a server socket to accept client connection requests final ServerSocket serSock=new ServerSocket(server_port); final Logger logger=Logger.getLogger("thredPoolLog"); //spawn a fixed number of threds to service clients for(int i=0;i<threadpool_size;i++){ Runnable run=new Runnable(){ public void run() { while(true){ try { Socket clientSock=serSock.accept(); /** * 把它交給一個獨立的handler處理 * 你可以將handler定義為一個獨立的線程(注意效能) * 或者定義為一個靜態方法(注意並發的同步問題)。 */ } catch (IOException e) { logger.log(Level.WARNING,"Client accept failed",e); } } } }; Thread t=new Thread(run,"Thread-"+i); t.start(); logger.info("create and start a thread named "+t.getName()); } } }
這裡我為accept()方法加上了線程池,我們發現,我們不必擔心少量的多個客戶並發串連的問題了,因為有多個線程對應的accept()在等待用戶端串連進來。一旦客戶成功串連進來,該線程重新返回線程池。如果並發訪問的用戶端超過線程池的size,那麼串連請求將在網路中形成一個隊列等待,這明顯是不利於維護的,並且線程的大小沒有適應性,因為它總是一成不變的。
並且串連進來的用戶端顯然不能交給單獨的線程來處理,否則我們控制線程數量過多造成的效能瓶頸的初衷將無疾而終。不過,如果給串連進來的用戶端建立一個線程池,就要考慮到維護問題,我們建立一個等待隊列,來維護那些超過用戶端線程池size的線程。這樣將引起一致命的問題,如果其中有幾個正在接受服務的線程阻塞等待或者由於未捕獲異常死亡,在等待隊列中的線程將因遲遲得不到資源而被餓死。
線程池的初步使用出現了這麼多問題,問題總是伴隨著被解決的可能性誕生的,就像出現了怪獸總會出現奧特曼一樣,於是我們的英雄又出現了-“Executor”,它是系統提供的,它可以協助我們來管理線程池。
本文轉自www.35java.com