• 多任务处理:线程池


    线程池

     每个新线程都会消耗系统资源:创建一个线程将占用CPU周期,而且每个线程都自己的数据结构(如,栈)也要消耗系统内存。另外,当一个线程阻塞(block)时,JVM将保存其状态,选择另外一个线程运行,并在上下文转换(context switch)时恢复阻塞线程的状态。随着线程数的增加,线程将消耗越来越多的系统资源。这将最终导致系统花费更多的时间来处理上下文转换和线程管理,更少的时间来对连接进行服务。那种情况下,加入一个额外的线程实际上可能增加客户端总服务时间。

     我们可以通过限制总线程数并重复使用线程来避免这个问题。与为每个连接创建一个新的线程不同,服务器在启动时创建一个由固定数量线程组成的线程池(thread pool)。当一个新的客户端连接请求传入服务器,它将交给线程池中的一个线程处理。当该线程处理完这个客户端后,又返回线程池,并为下一次请求处理做好准备。如果连接请求到达服务器时,线程池中的所有线程都已经被占用,它们则在一个队列中等待,直到有空闲的线程可用。

     与一客户一线程服务器一样,线程池服务器首先创建一个ServerSocket实例。然后创建N个线程,每个线程都反复循环,从(共享的)ServerSocket实例接收客户端连接。当多个线程同时调用同一个ServerSocket实例的accept()方法时,它们都将阻塞等待,直到一个新的连接成功建立。然后系统选择一个线程,新建立的连接对应的Socket实例则只在选中的线程中返回。其他线程则继续阻塞,直到成功建立下一个连接和选中另一个幸运的线程。

    由于线程池中的所有线程都反复循环,一个接一个地处理客户端连接,线程池服务器的行为就像是一组迭代服务器。与一客户一线程服务器不同,线程池中的线程在完成对一个客户端的服务后并不终止,相反,它又重新开始在accept()方法上阻塞等待。TCPEchoServerPool.java中演示了一个线程池的例子。

    TCPEchoServerPool.java

    0 import java.io.IOException;

    1 import java.net.ServerSocket;

    2 import java.net.Socket;

    3 import java.util.logging.Level;

    4 import java.util.logging.Logger;

    5

    6 public class TCPEchoServerPool {

    7

    8 public static void main(String[] args) throws

    IOException {

    9

    10 if (args.length != 2) { // Test for correct # of args

    11 throw new IllegalArgumentException("Parameter(s):

    <Port> <Threads>");

    12 }

    13

    14 int echoServPort = Integer.parseInt(args[0]); // Server

    port

    15 int threadPoolSize = Integer.parseInt(args[1]);

    16

    17 // Create a server socket to accept client connection

    requests

    18 final ServerSocket servSock = new

    ServerSocket(echoServPort);

    19

    20 final Logger logger = Logger.getLogger("practical");

    21

    22 // Spawn a fixed number of threads to service clients

    23 for (int i = 0; i < threadPoolSize; i++) {

    24 Thread thread = new Thread() {

    25 public void run() {

    26 while (true) {

    27 try {

    28 Socket clntSock = servSock.accept(); // Wait for a

    connection

    29 EchoProtocol.handleEchoClient(clntSock, logger); //

    Handle it

    30 } catch (IOException ex) {

    31 logger.log(Level.WARNING, "Client accept failed", ex);

    32 }

    33 }

    34 }

    35 };

    36 thread.start();

    37 logger.info("Created and started Thread = " +

    thread.getName());

    38 }

    39 }

    40 }

     

    TCPEchoServerPool.java

    1.设置:第10-20

    要侦听的端口号和线程的数量都作为参数传递给main()。对参数进行解析后再创建

    ServerSocket Logger实例。注意要它们都必须声明为常量(final),因为它们将在下面创

    建的匿名类中引用。

    2.创建并启动threadPoolSize个新线程:第23-38

    循环的每一次迭代都会创建一个继承于Thread的匿名类的实例。当调用该实例的start()方法时,这个线程就会执行该匿名类的run()方法。run()方法将反复循环,接受客户端的连接请求,并传递给EchoProtocol进行处理。

    接受连接请求:第28

     由于有N个不同线程在执行同一个循环,那么最多有N个线程在servSockaccept()方法上阻塞等待传入的连接请求。对于任何一个连接,系统保证了只要一个线程能够获得其对应的Socket。在一个客户端连接被创建时,如果没有线程在accept()方法上阻塞等待(即,所有线程都在忙着为其他连接服务),系统则将新的连接排列在一个队列中,直到下一次调accept()方法(见第6.4.1节)。

    将客户端套接字传递给EchoProtocol.handleEchoClient()方法:第29 

    handleEchoClient()方法中封装了协议的详细内容。该方法在连接处理完成后将相关信息写入日志,处理过程中遇到的异常也将写入日志。

    处理accept()方法抛出的异常:第31

    由于线程的重复使用,线程池的方法只需要付出创建N次线程的系统开销,而与客户端连接总数无关。由于可以控制最大并发执行线程数,我们就可以控制线程的调度和资源开销。当然,如果我们创建的线程太少,客户端还是有可能等很长时间才获得服务,因此,线程池的大小需要根据负载情况进行调整,以使客户端连接的时间最短。理想的情况是有一个调度工具,可以在系统负载增加时扩展线程池的大小(低于大小上限),负载较轻时缩减线程池的大小。Java恰好就有这种工具,我们将在下一节进行介绍。

    相关下载:

    Java_TCPIP_Socket编程(doc)

    http://download.csdn.net/detail/undoner/4940239

     

    文献来源:

    UNDONER(小杰博客) :http://blog.csdn.net/undoner

    LSOFT.CN(琅软中国) :http://www.lsoft.cn

  • 相关阅读:
    对于动态建立索引的禁止方法
    在 Lotus Notes 中设置邮件定时发送的方法及代理功能介绍
    Lotus Domino中使用Xpage技术打造通讯录
    利用 LotusScript 实现 Microsoft Word 文档在公文流转中的公文留痕
    Ext.Template模板进阶
    Ext.template的使用
    Domino中Xpage和自定义控件的使用
    利用 LotusScript 灵活操作 Lotus Notes 富文本域
    ExtJs2.0学习系列(11)Ext.XTemplate
    利用 XForms 和 ODF 实现交互式办公文档
  • 原文地址:https://www.cnblogs.com/wuyida/p/6301069.html
Copyright © 2020-2023  润新知