如果你用单线程写Socket,为什么要折腾?--单线程、多线程、线程池

2010-12-14 21:05

如果你用单线程写Socket,为什么要折腾?--单线程、多线程、线程池

by

at 2010-12-14 13:05:25

original http://www.javaeye.com/topic/841706

在开发Socket项目的时候,如果是开发一个自己玩玩,当然不用考虑效率、安全性等问题,可是如果是一个企业级的,你就不得不关注这几点。本系列文章将我们实验室里的Socket程序变成企业级的应用。
NIO编程肯定是一个很好的解决方案,不过这部分留在以后讨论。今天我想说说如何让你的阻塞的Scoket程序高效、安全的跑起来。
一开始,大家会编出一个单线程的Scoket程序,然后我们发现这个程序根本不能够连接多个客户端,于是我们引入“多线程”,使我们的程序能够同时处理多个客户端。
我相信,到现在为止如果没有深入研究过Socket编程,大家一般还是停留在“一客户一线程”的初级模式。如果是个位数的客户,当然你不会发现什么明显的性能问题。但是如果你的客户连接数量达到百位级,我靠,你的CPU就关顾着在各个线程间切换,你的内存似乎也有些吃不消了(每个线程都有自己独立的内存),更多的系统资源的消耗,更多的线程上下文转换,更复杂的线程管理(OS有一套自己的机制),将拖垮你的application。再加上多客户端尝试并发连接,及时响应客户端的连接将变得像癞蛤蟆追求天鹅一样不给力,因为线程的创建将占用服务器大量的CPU周期。
单线程多线程我们没法解决的问题,必然有新的英雄站出来解决,他就是“线程池”。

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”,它是系统提供的,它可以帮助我们来管理线程池。
如何管理,待我吃完饭回来继续……(见下一篇)

      <br><br>
      作者: <a href="http://jiadongkai-sina-com.javaeye.com">贾懂凯</a> 
      <br>
      声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!
      <br><br>
      <span style="color:red">
        <a href="http://www.javaeye.com/topic/841706" style="color:red">已有 <strong>0</strong> 人发表回复,猛击-&gt;&gt;<strong>这里</strong>&lt;&lt;-参与讨论</a>
      </span>
      <br><br><br>

JavaEye推荐