网站首页
JSP空间
动态资讯
开源项目
技术文档
资源下载
J2EE资源
客户论坛
在线支付
 
  技术文档>>JAVA>>新手入门>>基础入门>查看文档  
  java语言深入 多线程程序模型研究     
  文章作者:未知  文章来源:水木森林  
  查看:89次  录入:管理员--2007-11-17  
 

    多线程是较复杂程序设计过程中不可缺少的一部分。为了提高应用程序运行的性能,采用多线程的设计是一种比较可行的方案。本文通过介绍使用java编写的扫描计算机端口的实例,来说明多线程设计中应注意的问题,以及得出经常使用的多线程模型。

    本文要求读者具备一定的java语言基础,对socket有一定的了解。本文的所有程序在java sdk 1.4.2编译通过并能正常运行。

    现在,我们需要对一台主机扫描其端口,找出哪些端口是open的状态。我们先采用单线程进行处理,程序代码如下:
-------------------------------------------------------------------------------------------------------
import java.io.ioexception;
import java.net.socket;
import java.net.unknownhostexception;

public class portscannersinglethread {
    public static void main(string[] args) {
        string host = null;        //第一个参数,目标主机。
        int beginport = 1;         //第二个参数,开始端口。
        int endport = 65535;       //第三个参数,结束端口。
        try{
            host = args[0];
            beginport = integer.parseint(args[1]);
            endport = integer.parseint(args[2]);
            if(beginport <= 0 || endport >= 65536 || beginport > endport){
                throw new exception("port is illegal");
            }
        }catch(exception e){
            system.out.println("usage: java portscannersinglethread host beginport endport");
            system.exit(0);
        }
        

        for (int i = beginport; i <= endport; i++) {
            try {
                socket s = new socket(host, i);
                system.out.println("the port " + i + " is opened at " + host);
            }catch (unknownhostexception ex) {
                system.err.println(ex);
                break;
            }catch (ioexception ex) {
            }
        }
    }
}
--------------------------------------------------------------------------------------------------------
    在以上程序中,通过java.net.socket类来识别端口是否是open状态。程序接受3个参数,第一个参数是主机ip,第二和第三个参数是需要扫描的起始和中止的端口号(1~65535)。本程序(java portscannersinglethread 10.1.1.1 1 1000)运行结果如下:
the port 25 is opened at 10.1.1.182
the port 110 is opened at 10.1.1.182
the port 135 is opened at 10.1.1.182
...

    但是,以上程序运行效率实在不敢恭维,把目标主机端口扫描一遍需要十几分钟甚至更长,估计没有哪个用户可以忍受这样的效率。

    所以,提高程序处理效率是必须的,下面的程序通过多线程的方法来进行处理。程序代码如下:
----------------------------------------------------------------------------------------------------------
import java.io.ioexception;
import java.net.socket;
import java.net.unknownhostexception;

public class portscannermultithread {
    public static void main(string[] args) {
        string host = null;
        int beginport = 1;
        int endport = 65535;
        try{
            host = args[0];
            beginport = integer.parseint(args[1]);
            endport = integer.parseint(args[2]);
            if(beginport <= 0 || endport >= 65536 || beginport > endport){
                throw new exception("port is illegal");
            }
        }catch(exception e){
            system.out.println("usage: java portscannersinglethread host beginport endport");
            system.exit(0);
        }
        

        for (int i = beginport; i <= endport; i++) {
            portprocessor pp = new portprocessor(host,i);      //一个端口创建一个线程
            pp.start();
        }
    }
}

class portprocessor extends thread{
    string host;
    int port;
    
    portprocessor(string host, int port){
        this.host = host;
        this.port = port;
    }
    
    public void run(){
        try{
            socket s = new socket(host,port);
            system.out.println("the port " + port + " is opened at " + host);
        }catch(unknownhostexception ex){
            system.err.println(ex);
        }catch(ioexception ioe){
        }
    }
}


    以上程序在for循环结构中创建portprocessor对象,portprocessor类是线程类,其关键的socket在public void run()方法中实现。此程序比第一个单线程的程序运行效率提高很多倍,几乎在几秒钟内得出结果。所以可见多线程处理是何等的重要。
程序(java portscannermultithread 10.1.1.100 1 1000)运行结果如下:
the port 25 is opened at 10.1.1.100
the port 42 is opened at 10.1.1.100
the port 88 is opened at 10.1.1.100
...

    仔细对第2个程序分析,不难发现其中的问题:创建的线程个数是不固定的,取决于输入的第二和第三个参数。如果扫描1~100端口,那么主线程就产生100个线程来分别处理;如果扫描1~10000端口,主线程就会产生10000个线程来进行处理。在jvm中创建如此多的线程同样会带来性能上的问题,因为线程的创建和消失都是需要花费系统资源的。所以以上的第二个程序也存在明显的不足。

    所以,我们需要一个确定数量的线程在jvm中运行,这样就需要了解“线程池”(threadpool)的概念。线程池在多线程程序设计中是比不可少的,而且初学者不太容易掌握,下面通过对线程池的介绍,结合第3和第4个程序,引出两种常用的线程池模型。

    第一种实现线程池的方法是:创建一个”池“,在”池“中增加要处理的数据对象,然后创建一定数量的线程,这些线程对”池“中的对象进行处理。当”池“是空的时候,每个线程处于等待状态;当往”池“里添加一个对象,通知所有等待的线程来处理(当然一个对象只能有一个线程来处理)。

    第二种方法是:同样创建一个”池“,但是在”池“中放的不是数据对象,而是线程,可以把”池“中的一个个线程比喻成一个个”工人“,当没有任务的时候,”工人“们严阵以待;当给”池“添加一个任务后,”工人“就开始处理并直到处理完成。

    在第3个程序中,定义了list类型的entries作为“池”,这个“池”用来保存需要扫描的端口,list中的元素必须是object类型,不能用基本数据类型int往池里添加,而需要用使用integer。在processmethod()方法中,首先就启动一定数量的portthread线程,同时在while循环中通过entries.add(0, new integer(port))往“池”里添加对象。在portthread类的run()方法中通过entry = (integer)entries.remove(entries.size()-1);取得“池”中的对象,转换成int后传递给socket构造方法。
    第3个程序如下:
-----------------------------------------------------------------------------------------------------------------------
import java.io.ioexception;
import java.net.inetaddress;
import java.net.socket;
import java.net.unknownhostexception;
import java.util.collections;
import java.util.linkedlist;
import java.util.list;

public class portscanner {
    private list entries = collections.synchronizedlist(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){
        this.remote = addr;
        this.beginport = beginport;
        this.endport = endport;
        this.numofthreads = numofthreads;    
    }
    
    public void processmethod(){
        for(int i = 0; i < numofthreads; i++){          //创建一定数量的线程并运行
            thread t = new portthread(remote, entries, this);
            t.start();
        }
        
        port = beginport;
        
        while(true){
            if(entries.size() > numofthreads){
                try{
                    thread.sleep(1000);      //”池“中的内容太多的话就sleep
                }catch(interruptedexception ex){
                    
                }
                continue;
            }
            
            synchronized(entries){
                if(port > endport) break;
                entries.add(0, new integer(port));  //往”池“里添加对象,需要使用int对应的integer类
                entries.notifyall();
                port++;
            }
        }
    }
    
    public static void main(string[] args) {
        string host = null;
        int beginport = 1;
        int endport = 65535;
        int nthreads = 100;
        try{
            host = args[0];
            beginport = integer.parseint(args[1]);
            endport = integer.parseint(args[2]);
            nthreads = integer.parseint(args[3]);
            if(beginport <= 0 || endport >= 65536 || beginport > endport){
                throw new exception("port is illegal");
            }
        }catch(exception e){
            system.out.println("usage: java portscannersinglethread host beginport endport nthreads");
            system.exit(0);
        }
        
        try{
            portscanner scanner = new portscanner(inetaddress.getbyname(host), beginport, endport, nthreads);
            scanner.processmethod();
        }catch(unknownhostexception ex){
        }    
    }
}

class portthread extends thread{
    private inetaddress remote;
    private list entries;
    portscanner scanner;
    
    portthread(inetaddress add, list entries, portscanner scanner){
        this.remote = add;
        this.entries = entries;
        this.scanner = scanner;
    }
            
    public void run(){
        integer entry;
        while(true){
            synchronized(entries){
                while(entries.size() == 0){
                    if(scanner.isfinished()) return;
                    try{
                        entries.wait();           //”池“里没内容就只能等了
                    }catch(interruptedexception ex){
                    }
                }
                entry = (integer)entries.remove(entries.size()-1);  //把”池“里的东西拿出来进行处理
            }
            
            socket s = null;
            
            try{
                s = new socket(remote, entry.intvalue());
                system.out.println("the port of " + entry.tostring() + " of the remote " + remote +" is opened.");
            
            }catch(ioexception e){
            }finally{
                try{
                    if(s != null) s.close();
                }catch(ioexception e){
                    
                }
            }
        }
    }
}


    以上程序需要4个参数,输入java portscanner 10.1.1.182 1 10000 100运行(第4个参数是线程数),结果前两个程序一样,但是速度比第一个要快,可能比第二个要慢一些。

    第3个程序是把端口作为“池”中的对象,下面我们看第4个实现方式,把“池”里面的对象定义成是线程类,把具体的任务定义成”池“中线程类的参数。第4个程序有2个文件组成,分别是threadpool.java和portscannerbythreadpool.java.
    threadpool.java文件内容如下:
-----------------------------------------------------------
import java.util.linkedlist;

public class threadpool{
    private final int nthreads;
    private final poolworker[] threads;
    private final linkedlist queue;

    public threadpool(int nthreads){
        this.nthreads = nthreads;
        queue = new linkedlist();
        threads = new poolworker[nthreads];

        for (int i=0; i<nthreads; i++) {
            threads[i] = new poolworker();
            threads[i].start();
        }
    }

    public void execute(runnable r) {
        synchronized(queue) {
            queue.addlast(r);
            queue.notifyall();
        }
    }

    private class poolworker extends thread {
        public void run() {
            runnable r;

            while (true) {
                synchronized(queue) {
                    while (queue.isempty()) {
                        try{
                            queue.wait();
                        }catch (interruptedexception ignored){
                        }
                    }

                    r = (runnable) queue.removefirst();
                }

                try {
                    r.run();
                }
                catch (runtimeexception e) {
                }
            }
        }
    }
}
------------------------------------------------------------------------------------------------------------------
    在threadpool.java文件中定义了2个类:threadpool和poolworker。threadpool类中的nthreads变量表示线程数,poolworker数组类型的threads变量表示线程池中的“工人”,这些“工人”的工作就是一直循环处理通过queue.addlast(r)加入到“池”中的任务。
    portscannerbythreadpool.java文件内容如下:
-------------------------------------------------------------------------------------------------------------------
import java.io.ioexception;
import java.net.inetaddress;
import java.net.socket;

public class portscannerbythreadpool {
    public static void main(string[] args) {
        string host = null;
        int beginport = 1;
        int endport = 65535;
        int nthreads = 100;
        try{
            host = args[0];
            beginport = integer.parseint(args[1]);
            endport = integer.parseint(args[2]);
            nthreads = integer.parseint(args[3]);
            if(beginport <= 0 || endport >= 65536 || beginport > endport){
                throw new exception("port is illegal");
            }
        }catch(exception e){
            system.out.println("usage: java portscannersinglethread host beginport endport nthreads");
            system.exit(0);
        }
        
        threadpool tp = new threadpool(nthreads);
        
        for(int i = beginport; i <= endport; i++){
            scanner ps = new scanner(host,i);
            tp.execute(ps);
        }
    }
}

    
class scanner implements runnable{
    string host;    
    int port;
        
    scanner(string host, int port){
        this.host = host;
        this.port = port;
    }
        
    public void run(){
        socket s = null;
        try{
            s = new socket(inetaddress.getbyname(host),port);
            system.out.println("the port of " + port + " is opened.");
        }catch(ioexception ex){
        }finally{
            try{
                if(s != null) s.close();
            }catch(ioexception e){
            }
        }
    }
}
---------------------------------------------------------------------------------------------------------------------
    portscannerbythreadpool是主程序类,处理输入的4个参数(和第3个程序是一样的):主机名、开始端口、结束端口和线程数。scanner类定义了真正的”任务“。在portscannerbythreadpool中通过new threadpool(nthreads)创建threadpool对象,然后在for循环中通过new scanner(host,i)创建”任务“对象,再通过tp.execute(ps)把”任务“对象添加到”池“中。

    读者可以编译运行第4个程序,得出的结果和前面的是一样的。但是第4和第3个程序之间最大的差别就是:第4个程序会一直运行下去,不会自动结束。在第3个程序中存在一个isfinished()方法,可以用来判断任务是否处理完毕,而第4个程序中没有这样做。请读者自己思考这个问题。

    在第3和第4个程序中,我们可以概括出多线程的模型。第3个程序的线程”池“里装的要处理的对象,第4个程序的线程”池“里装的是”工人“,还需要通过定义”任务“并给把它”派工“给”工人“。我个人比较偏好后者的线程池模型,虽然类的个数多了几个,但逻辑很清晰。不管怎样,第3和第4个程序中关键的部分都大同小异,就是2个synchronized程序块中的内容,如下(第4个程序中的):
synchronized(queue) {
    queue.addlast(r);
    queue.notifyall();
}

synchronized(queue) {
    while (queue.isempty()) {
        try{
            queue.wait();
        }catch (interruptedexception ignored){
        }
    }

    r = (runnable) queue.removefirst();
}

    一般拿synchronized用来定义方法或程序块,这样可以在多线程同时访问的情况下,保证在一个时刻只能有一个线程对这部分内容进行访问,避免了数据出错。在第3个程序中通过list entries = collections.synchronizedlist(new linkedlist())来定义”池“,在第4个程序中直接用linkedlist queue,都差不多,只是collections.synchronizedlist()可以保证”池“的同步,其实”池“里的内容访问都是在synchronized定义的程序块中,所以不用collections.synchronizedlist()也是可以的。

    wait()和notifyall()是很重要的,而且这2个方法是object基类的方法,所以任何一个类都是可以使用的。这里说明一个可能产生混淆的问题:queue.wait()并不是说queue对象需要进行等待,而是说queue.wait()所在的线程需要进行等待,并且释放对queue的锁,把对queue的访问权交给别的线程。如果读者对这2个方法难以理解,建议参考jdk的文档说明。

    好了,通过以上4个例子的理解,读者应该能对多线程的程序设计有了一定的理解。第3和第4个程序对应线程模型是非常重要的,可以说是多线程程序设计过程中不可或缺的内容。

    如果读者对以上的内容有任何疑问,可以和我联系,qianh@cntmi.com 版权所有,严禁转载

参考资料:
1、《java networking programming, 3rd》written by elliotte rusty harold, published by o'reilly,2004  
2、“thread pools and work queues” written by brian goetz, principal consultant, quiotix corp.

 
 
上一篇: 用java线程获取优异性能(ii)??使用同步连载线程访问关键代码部份    下一篇: string类使用的例子(3)
  相关文档
java新手看招 常用开发工具介绍 11-17
java入门(6)java数据类型 11-17
自动化weblogic平台应用程序供应 11-17
java反编译的研究 11-16
jfc/swing活学活用之定制jlist显示 11-17
tolowercase 方法 11-16
java初学者入门要注意的基础知识 11-16
两种方法定位java应用程序瓶颈 (1) 11-17
java:配置文件读取器 11-17
java基础:java及相关字符集编码问题研究 12-05
基于java技术的搜索引擎的研究与实现 11-17
struts1.0学习文档-初学者入门 11-17
只支持单表映射的持久化框架──easydbo 02-20
在组合模式中实现访问者(visitor)模式 01-30
深入了解j2me的几个重要概念 11-17
java类装载体系中的隔离性 11-17
crystal report ras9 java web开发总结 11-17
scjp认证套题解析之十 11-16
JAVA中用接口实现多继承和多态的方法 04-14
xml和j2ee的组合技术 11-17
返回首页 | 关于我们 | J网章程 | JSP空间合租 | 客服中心 | 免责声明 | 常见问题 | 参观机房
本站主机空间代理至厦门市华众网络科技有限公司
《中华人民共和国增值电信业务经营许可证》
编号:闽B2-20050079
@2005-2008福建JSP技术网 版权所有 闽ICP备05000928号
技术电话:13616026886
邮箱:admin@fjjsp.com 站长QQ,点击这里给我发消息