论坛首页 Java企业应用论坛

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

浏览 10414 次
精华帖 (1) :: 良好帖 (1) :: 新手帖 (18) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-12-14   最后修改:2010-12-14
在开发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”,它是系统提供的,它可以帮助我们来管理线程池。
如何管理,待我吃完饭回来继续……(见下一篇)
   发表时间:2010-12-15  
这个也叫线程池啊,你应该使用concurrent包的ThreadPoolExecutor
0 请登录后投票
   发表时间:2010-12-15  
yumcn.com 写道
这个也叫线程池啊,你应该使用concurrent包的ThreadPoolExecutor

在我的下一篇文章里有写,这篇只是引入。
0 请登录后投票
   发表时间:2010-12-15  
greedsluck 写道
嗯,不错,这样行了吧

谢谢捧场,呵呵~~
0 请登录后投票
   发表时间:2010-12-15  
我什么也没看到。。。
0 请登录后投票
   发表时间:2010-12-15  
用Executor不见得就比你现在的方法好。
等你贴出线程池的用法后再看看有没有我预计到的问题出现吧。
给你个提示,网络连接是个比线程更重量级的资源。
0 请登录后投票
   发表时间:2010-12-15  
下一篇昨天就写了大家可能没看见。链接:http://www.iteye.com/topic/842139#1804671
0 请登录后投票
   发表时间:2010-12-15  
taolei0628 写道
用Executor不见得就比你现在的方法好。
等你贴出线程池的用法后再看看有没有我预计到的问题出现吧。
给你个提示,网络连接是个比线程更重量级的资源。

确实如此,三次握手会占比较长时间,只有到第三次握手完成才会返回socket实例。我觉得如果并发访问要求比较高的话,用我上面的代码中的线程池方法应该能增加效率。不过,如果在accept方法上加一个超时机制效果会更好。
求taolei0628指点~~
0 请登录后投票
   发表时间:2010-12-16  
贾懂凯 写道
taolei0628 写道
用Executor不见得就比你现在的方法好。
等你贴出线程池的用法后再看看有没有我预计到的问题出现吧。
给你个提示,网络连接是个比线程更重量级的资源。

确实如此,三次握手会占比较长时间,只有到第三次握手完成才会返回socket实例。我觉得如果并发访问要求比较高的话,用我上面的代码中的线程池方法应该能增加效率。不过,如果在accept方法上加一个超时机制效果会更好。
求taolei0628指点~~

楼主为何不考虑用现成的框架呢?我们项目也需要用到socket编程,前一阵子用过netty,后来发现XSocket更好用。
0 请登录后投票
   发表时间:2010-12-16  
zhaoxin1943 写道
贾懂凯 写道
taolei0628 写道
用Executor不见得就比你现在的方法好。
等你贴出线程池的用法后再看看有没有我预计到的问题出现吧。
给你个提示,网络连接是个比线程更重量级的资源。

确实如此,三次握手会占比较长时间,只有到第三次握手完成才会返回socket实例。我觉得如果并发访问要求比较高的话,用我上面的代码中的线程池方法应该能增加效率。不过,如果在accept方法上加一个超时机制效果会更好。
求taolei0628指点~~

楼主为何不考虑用现成的框架呢?我们项目也需要用到socket编程,前一阵子用过netty,后来发现XSocket更好用。

框架易用,底层难把握,况且需求各异要求设计应时而变。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics