H5W3
当前位置:H5W3 > 其他技术问题 > 正文

Java语言深入 多线程程序模型研究

多线程是较复杂程序设计过程中不可缺少的一部分。为了提高应用程序运行的性能,采用多线程的设计是一种比较可行的方案。本文通过介绍使用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.182The port 110 is opened at 10.1.1.182The 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){        }    }}

以上程序需要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.

本文地址:H5W3 » Java语言深入 多线程程序模型研究

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址