熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

Java語言深入 多線程程序模型研究

2013-11-23 19:45:44  來源: Java高級技術 

  多線程是較復雜程序設計過程中不可缺少的一部分為了提高應用程序運行的性能采用多線程的設計是一種比較可行的方案本文通過介紹使用Java編寫的掃描計算機端口的實例來說明多線程設計中應注意的問題以及得出經常使用的多線程模型

    本文要求讀者具備一定的Java語言基礎對Socket有一定的了解本文的所有程序在Java SDK 編譯通過並能正常運行

    現在我們需要對一台主機掃描其端口找出哪些端口是open的狀態我們先采用單線程進行處理程序代碼如下

import javaioIOException;
import Socket;
import UnknownHostException;

public class PortScannerSingleThread {
    public static void main(String[] args) {
        String host = null;        //第一個參數目標主機
        int beginport = ;         //第二個參數開始端口
        int endport = ;       //第三個參數結束端口
        try{
            host = args[];
            beginport = IntegerparseInt(args[]);
            endport = IntegerparseInt(args[]);
            if(beginport <=  || endport >=  || beginport > endport){
                throw new Exception(Port is illegal);
            }
        }catch(Exception e){
            Systemoutprintln(Usage: java PortScannerSingleThread host beginport endport);
            Systemexit();
        }
        

        for (int i = beginport; i <= endport; i++) {
            try {
                Socket s = new Socket(host i);
                Systemoutprintln(The port  + i +  is opened at  + host);
            }catch (UnknownHostException ex) {
                Systemerrprintln(ex);
                break;
            }catch (IOException ex) {
            }
        }
    }
}

    在以上程序中通過Socket類來識別端口是否是open狀態程序接受個參數第一個參數是主機IP第二和第三個參數是需要掃描的起始和中止的端口號(~本程序(java PortScannerSingleThread   )運行結果如下
The port  is opened at 
The port  is opened at 
The port  is opened at 


    但是以上程序運行效率實在不敢恭維把目標主機端口掃描一遍需要十幾分鐘甚至更長估計沒有哪個用戶可以忍受這樣的效率

    所以提高程序處理效率是必須的下面的程序通過多線程的方法來進行處理程序代碼如下

import javaioIOException;
import Socket;
import UnknownHostException;

public class PortScannerMultiThread {
    public static void main(String[] args) {
        String host = null;
        int beginport = ;
        int endport = ;
        try{
            host = args[];
            beginport = IntegerparseInt(args[]);
            endport = IntegerparseInt(args[]);
            if(beginport <=  || endport >=  || beginport > endport){
                throw new Exception(Port is illegal);
            }
        }catch(Exception e){
            Systemoutprintln(Usage: java PortScannerSingleThread host beginport endport);
            Systemexit();
        }
        

        for (int i = beginport; i <= endport; i++) {
            PortProcessor pp = new PortProcessor(hosti);      //一個端口創建一個線程
            ppstart();
        }
    }
}

class PortProcessor extends Thread{
    String host;
    int port;
    
    PortProcessor(String host int port){
        thishost = host;
        thisport = port;
    }
    
    public void run(){
        try{
            Socket s = new Socket(hostport);
            Systemoutprintln(The port  + port +  is opened at  + host);
        }catch(UnknownHostException ex){
            Systemerrprintln(ex);
        }catch(IOException ioe){
        }
    }
}

  以上程序在for循環結構中創建PortProcessor對象PortProcessor類是線程類其關鍵的Socket在public void run()方法中實現此程序比第一個單線程的程序運行效率提高很多倍幾乎在幾秒鐘內得出結果所以可見多線程處理是何等的重要
程序(java PortScannerMultiThread   )運行結果如下
The port  is opened at 
The port  is opened at 
The port  is opened at 


    仔細對第個程序分析不難發現其中的問題創建的線程個數是不固定的取決於輸入的第二和第三個參數如果掃描端口那麼主線程就產生個線程來分別處理如果掃描~端口主線程就會產生個線程來進行處理在JVM中創建如此多的線程同樣會帶來性能上的問題因為線程的創建和消失都是需要花費系統資源的所以以上的第二個程序也存在明顯的不足

    所以我們需要一個確定數量的線程在JVM中運行這樣就需要了解線程池(ThreadPool)的概念線程池在多線程程序設計中是比不可少的而且初學者不太容易掌握下面通過對線程池的介紹結合第和第個程序引出兩種常用的線程池模型

    第一種實現線程池的方法是創建一個中增加要處理的數據對象然後創建一定數量的線程這些線程對中的對象進行處理是空的時候每個線程處於等待狀態當往裡添加一個對象通知所有等待的線程來處理(當然一個對象只能有一個線程來處理)

    第二種方法是同樣創建一個但是在中放的不是數據對象而是線程可以把中的一個個線程比喻成一個個工人當沒有任務的時候工人們嚴陣以待當給添加一個任務後工人就開始處理並直到處理完成

    在第個程序中定義了List類型的entries作為這個用來保存需要掃描的端口List中的元素必須是Object類型不能用基本數據類型int往池裡添加而需要用使用Integer在processMethod()方法中首先就啟動一定數量的PortThread線程同時在while循環中通過entriesadd( new Integer(port))往裡添加對象在PortThread類的run()方法中通過entry = (Integer)entriesremove(entriessize());取得中的對象轉換成int後傳遞給Socket構造方法
    第個程序如下

import javaioIOException;
import InetAddress;
import Socket;
import UnknownHostException;
import javautilCollections;
import javautilLinkedList;
import javautilList;

public class PortScanner {
    private List entries = CollectionssynchronizedList(new LinkedList());  //這個比較特別
    int numofthreads;
    static int port;
    int beginport;
    int endport;
    InetAddress remote = null;
    
    public boolean isFinished(){
        if(port >= endport){
            return true;
        }else{
            return false;
        }
    }
    
    PortScanner(InetAddress addr int beginport int endport int numofthreads){
        thisremote = addr;
        thisbeginport = beginport;
        thisendport = endport;
        thisnumofthreads = numofthreads;    
    }
    
    public void processMethod(){
        for(int i = ; i < numofthreads; i++){          //創建一定數量的線程並運行
            Thread t = new PortThread(remote entries this);
            tstart();
        }
        
        port = beginport;
        
        while(true){
            if(entriessize() > numofthreads){
                try{
                    Threadsleep();      //中的內容太多的話就sleep
                }catch(InterruptedException ex){
                    
                }
                continue;
            }
            
            synchronized(entries){
                if(port > endport) break;
                entriesadd( new Integer(port));  //往裡添加對象需要使用int對應的Integer類
                entriesnotifyAll();
                port++;
            }
        }
    }
    
    public static void main(String[] args) {
        String host = null;
        int beginport = ;
        int endport = ;
        int nThreads = ;
        try{
            host = args[];
            beginport = IntegerparseInt(args[]);
            endport = IntegerparseInt(args[]);
            nThreads = IntegerparseInt(args[]);
            if(beginport <=  || endport >=  || beginport > endport){
                throw new Exception(Port is illegal);
            }
        }catch(Exception e){
            Systemoutprintln(Usage: java PortScannerSingleThread host beginport endport nThreads);
            Systemexit();
        }
        
        try{
            PortScanner scanner = new PortScanner(InetAddressgetByName(host) beginport endport nThreads);
            scannerprocessMethod();
        }catch(UnknownHostException ex){
        }    
    }
}

class PortThread extends Thread{
    private InetAddress remote;
    private List entries;
    PortScanner scanner;
    
    PortThread(InetAddress add List entries PortScanner scanner){
        thisremote = add;
        thisentries = entries;
        thisscanner = scanner;
    }
            
    public void run(){
        Integer entry;
        while(true){
            synchronized(entries){
                while(entriessize() == ){
                    if(scannerisFinished()) return;
                    try{
                        entrieswait();           //裡沒內容就只能等了
                    }catch(InterruptedException ex){
                    }
                }
                entry = (Integer)entriesremove(entriessize());  //把裡的東西拿出來進行處理
            }
            
            Socket s = null;
            
            try{
                s = new Socket(remote entryintValue());
                Systemoutprintln(The port of  + entrytoString() +  of the remote  + remote + is opened);
            
            }catch(IOException e){
            }finally{
                try{
                    if(s != null) sclose();
                }catch(IOException e){
                    
                }
            }
        }
    }
}

  以上程序需要個參數輸入java PortScanner    運行(第個參數是線程數)結果前兩個程序一樣但是速度比第一個要快可能比第二個要慢一些

    第個程序是把端口作為中的對象下面我們看第個實現方式裡面的對象定義成是線程類把具體的任務定義成中線程類的參數個程序有個文件組成分別是ThreadPooljava和PortScannerByThreadPooljava
    ThreadPooljava文件內容如下

import javautilLinkedList;

public class ThreadPool{
    private final int nThreads;
    private final PoolWorker[] threads;
    private final LinkedList queue;

    public ThreadPool(int nThreads){
        thisnThreads = nThreads;
        queue = new LinkedList();
        threads = new PoolWorker[nThreads];

        for (int i=; i<nThreads; i++) {
            threads[i] = new PoolWorker();
            threads[i]start();
        }
    }

    public void execute(Runnable r) {
        synchronized(queue) {
            queueaddLast(r);
            queuenotifyAll();
        }
    }

    private class PoolWorker extends Thread {
        public void run() {
            Runnable r;

            while (true) {
                synchronized(queue) {
                    while (queueisEmpty()) {
                        try{
                            queuewait();
                        }catch (InterruptedException ignored){
                        }
                    }

                    r = (Runnable) queueremoveFirst();
                }

                try {
                    rrun();
                }
                catch (RuntimeException e) {
                }
            }
        }
    }
}

    在ThreadPooljava文件中定義了個類ThreadPool和PoolWorkerThreadPool類中的nThreads變量表示線程數PoolWorker數組類型的threads變量表示線程池中的工人這些工人的工作就是一直循環處理通過queueaddLast(r)加入到中的任務
    PortScannerByThreadPooljava文件內容如下

import javaioIOException;
import InetAddress;
import Socket;

public class PortScannerByThreadPool {
    public static void main(String[] args) {
        String host = null;
        int beginport = ;
        int endport = ;
        int nThreads = ;
        try{
            host = args[];
            beginport = IntegerparseInt(args[]);
            endport = IntegerparseInt(args[]);
            nThreads = IntegerparseInt(args[]);
            if(beginport <=  || endport >=  || beginport > endport){
                throw new Exception(Port is illegal);
            }
        }catch(Exception e){
            Systemoutprintln(Usage: java PortScannerSingleThread host beginport endport nThreads);
            Systemexit();
        }
        
        ThreadPool tp = new ThreadPool(nThreads);
        
        for(int i = beginport; i <= endport; i++){
            Scanner ps = new Scanner(hosti);
            tpexecute(ps);
        }
    }
}

  class Scanner implements Runnable{
    String host;    
    int port;
        
    Scanner(String host int port){
        thishost = host;
        thisport = port;
    }
        
    public void run(){
        Socket s = null;
        try{
            s = new Socket(InetAddressgetByName(host)port);
            Systemoutprintln(The port of  + port +  is opened);
        }catch(IOException ex){
        }finally{
            try{
                if(s != null) sclose();
            }catch(IOException e){
            }
        }
    }
}

    PortScannerByThreadPool是主程序類處理輸入的個參數(和第個程序是一樣的)主機名開始端口結束端口和線程數Scanner類定義了真正的任務在PortScannerByThreadPool中通過new ThreadPool(nThreads)創建ThreadPool對象然後在for循環中通過new Scanner(hosti)創建任務對象再通過tpexecute(ps)把任務對象添加到

    讀者可以編譯運行第個程序得出的結果和前面的是一樣的但是第和第個程序之間最大的差別就是個程序會一直運行下去不會自動結束在第個程序中存在一個isFinished()方法可以用來判斷任務是否處理完畢而第個程序中沒有這樣做請讀者自己思考這個問題

    在第和第個程序中我們可以概括出多線程的模型個程序的線程裡裝的要處理的對象個程序的線程裡裝的是工人還需要通過定義任務並給把它派工工人我個人比較偏好後者的線程池模型雖然類的個數多了幾個但邏輯很清晰不管怎樣和第個程序中關鍵的部分都大同小異就是個synchronized程序塊中的內容如下(第個程序中的)
synchronized(queue) {
    queueaddLast(r);
    queuenotifyAll();
}

synchronized(queue) {
    while (queueisEmpty()) {
        try{
            queuewait();
        }catch (InterruptedException ignored){
        }
    }

    r = (Runnable) queueremoveFirst();
}

    一般拿synchronized用來定義方法或程序塊這樣可以在多線程同時訪問的情況下保證在一個時刻只能有一個線程對這部分內容進行訪問避免了數據出錯在第個程序中通過List entries = CollectionssynchronizedList(new LinkedList())來定義在第個程序中直接用LinkedList queue都差不多只是CollectionssynchronizedList()可以保證的同步其實裡的內容訪問都是在synchronized定義的程序塊中所以不用CollectionssynchronizedList()也是可以的

    wait()和notifyAll()是很重要的而且這個方法是Object基類的方法所以任何一個類都是可以使用的這裡說明一個可能產生混淆的問題queuewait()並不是說queue對象需要進行等待而是說queuewait()所在的線程需要進行等待並且釋放對queue的鎖把對queue的訪問權交給別的線程如果讀者對這個方法難以理解建議參考JDK的文檔說明

    好了通過以上個例子的理解讀者應該能對多線程的程序設計有了一定的理解和第個程序對應線程模型是非常重要的可以說是多線程程序設計過程中不可或缺的內容

    如果讀者對以上的內容有任何疑問可以和我聯系 版權所有嚴禁轉載

參考資料
《Java Networking Programming rd》written by Elliotte Rusty Harold Published by OReilly  
Thread pools and work queues written by Brian Goetz Principal Consultant Quiotix Corp


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27410.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.