`

Java网络编程精解之ServerSocket用法详解二1 

    博客分类:
  • J2SE
阅读更多

3.6  创建多线程的服务器

在本书第1章的1.5.1节的例程1-2的EchoServer中,其service()方法负责接收客户连接,以及与客户通信。service()方法的处理流程如下:

while (true) { Socket socket=null; try { socket = serverSocket.accept();     //接收客户连接 //从Socket中获得输入流与输出流,与客户通信 … }catch (IOException e) { e.printStackTrace(); }finally { try{ if(socket!=null)socket.close();    //断开连接 }catch (IOException e) {e.printStackTrace();} } } 

EchoServer接收到一个客户连接,就与客户进行通信,通信完毕后断开连接,然后再接收下一个客户连接。假如同时有多个客户请求连接,这些客户就必须排队等候EchoServer的响应。EchoServer无法同时与多个客户通信。

许多实际应用要求服务器具有同时为多个客户提供服务的能力。HTTP服务器就是最明显的例子。任何时刻,HTTP服务器都可能接收到大量的客户请求,每个客户都希望能快速得到HTTP服务器的响应。如果长时间让客户等待,会使网站失去信誉,从而降低访问量。

可以用并发性能来衡量一个服务器同时响应多个客户的能力。一个具有好的并发性能的服务器,必须符合两个条件:

◆能同时接收并处理多个客户连接;

◆对于每个客户,都会迅速给予响应。

服务器同时处理的客户连接数目越多,并且对每个客户作出响应的速度越快,就表明并发性能越高。

用多个线程来同时为多个客户提供服务,这是提高服务器的并发性能的最常用的手段。本节将按照3种方式来重新实现EchoServer,它们都使用了多线程。

◆为每个客户分配一个工作线程。

◆创建一个线程池,由其中的工作线程来为客户服务。

◆利用JDK的Java类库中现成的线程池,由它的工作线程来为客户服务。

3.6.1  为每个客户分配一个线程

服务器的主线程负责接收客户的连接,每次接收到一个客户连接,就会创建一个工作线程,由它负责与客户的通信。以下是EchoServer的service()方法的代码:

public void service() { while (true) { Socket socket=null; try { socket = serverSocket.accept();      //接收客户连接 Thread workThread=new Thread(new Handler(socket));   //创建一个工作线程 workThread.start();        //启动工作线程 }catch (IOException e) { e.printStackTrace(); } } }

以上工作线程workThread执行Handler的run()方法。Handler类实现了Runnable接口,它的run()方法负责与单 个客户通信,与客户通信结束后,就会断开连接,执行Handler的run()方法的工作线程也会自然终止。如例程3-5所示是EchoServer类及 Handler类的源程序。

例程3-5  EchoServer.java(为每个任务分配一个线程)

package multithread1; import java.io.*; import java.net.*; public class EchoServer { private int port=8000; private ServerSocket serverSocket;

public EchoServer() throws IOException { serverSocket = new ServerSocket(port); System.out.println("服务器启动"); }

public void service() { while (true) { Socket socket=null; try { socket = serverSocket.accept();      //接收客户连接 Thread workThread=new Thread(new Handler(socket));   //创建一个工作线程 workThread.start();        //启动工作线程 }catch (IOException e) { e.printStackTrace(); } } }

public static void main(String args[])throws IOException { new EchoServer().service(); } }

class Handler implements Runnable{       //负责与单个客户的通信 private Socket socket; public Handler(Socket socket){ this.socket=socket; } private PrintWriter getWriter(Socket socket)throws IOException{…} private BufferedReader getReader(Socket socket)throws IOException{…} public String echo(String msg) {…} public void run(){ try { System.out.println("New connection accepted " + socket.getInetAddress() + ":" +socket.getPort()); BufferedReader br =getReader(socket); PrintWriter pw = getWriter(socket); String msg = null; while ((msg = br.readLine()) != null) {     //接收和发送数据,直到通信结束 System.out.println(msg); pw.println(echo(msg)); if (msg.equals("bye")) break; } }catch (IOException e) { e.printStackTrace(); }finally { try{ if(socket!=null)socket.close();       //断开连接 }catch (IOException e) {e.printStackTrace();} } } }

3.6.2  创建线程池

在3.6.1节介绍的实现方式中,对每个客户都分配一个新的工作线程。当工作线程与客户通信结束,这个线程就被销毁。这种实现方式有以下不足之处。

◆服务器创建和销毁工作线程的开销(包括所花费的时间和系统资源)很大。如果服务器需要与许多客户通信,并且与每个客户的通信时间都很短,那么有可能服务器为客户创建新线程的开销比实际与客户通信的开销还要大。

◆除了创建和销毁线程的开销之外,活动的线程也消耗系统资源。每个线程本身都会占用一定的内存(每个线程需要大约1M内存),如果同时有大量客户连接服务器,就必须创建大量工作线程,它们消耗了大量内存,可能会导致系统的内存空间不足。

◆如果线程数目固定,并且每个线程都有很长的生命周期,那么线程切换也是相对固定的。不同操作系统有不同的切换周期,一般在20毫秒左右。这里所说 的线程切换是指在Java虚拟机,以及底层操作系统的调度下,线程之间转让CPU的使用权。如果频繁创建和销毁线程,那么将导致频繁地切换线程,因为一个 线程被销毁后,必然要把CPU转让给另一个已经就绪的线程,使该线程获得运行机会。在这种情况下,线程之间的切换不再遵循系统的固定切换周期,切换线程的 开销甚至比创建及销毁线程的开销还大。

线程池为线程生命周期开销问题和系统资源不足问题提供了解决方案。线程池中预先创建了一些工作线程,它们不断从工作队列中取出任务,然后执行该任务。当工作线程执行完一个任务时,就会继续执行工作队列中的下一个任务。线程池具有以下优点:

◆减少了创建和销毁线程的次数,每个工作线程都可以一直被重用,能执行多个任务。

◆可以根据系统的承载能力,方便地调整线程池中线程的数目,防止因为消耗过量系统资源而导致系统崩溃。

如例程3-6所示,ThreadPool类提供了线程池的一种实现方案。

例程3-6  ThreadPool.java

package multithread2; import java.util.LinkedList; public class ThreadPool extends ThreadGroup { private boolean isClosed=false;     //线程池是否关闭 private LinkedList workQueue;   //表示工作队列 private static int threadPoolID;     //表示线程池ID private int threadID;      //表示工作线程ID

public ThreadPool(int poolSize) {    //poolSize指定线程池中的工作线程数目 super("ThreadPool-" + (threadPoolID++)); setDaemon(true); workQueue = new LinkedList();   //创建工作队列 for (int i=0; i new WorkThread().start();     //创建并启动工作线程 } /** 向工作队列中加入一个新任务,由工作线程去执行该任务 */ public synchronized void execute(Runnable task) { if (isClosed) {      //线程池被关则抛出

IllegalStateException异常 throw new IllegalStateException(); } if (task != null) { workQueue.add(task); notify();       //唤醒正在getTask()方法中等待任务的工作线程 } }

/** 从工作队列中取出一个任务,工作线程会调用此方法 */ protected synchronized Runnable getTask()throws InterruptedException{ while (workQueue.size() == 0) { if (isClosed) return null; wait();       //如果工作队列中没有任务,就等待任务 } return workQueue.removeFirst(); }

/** 关闭线程池 */ public synchronized void close() { if (!isClosed) { isClosed = true; workQueue.clear();     //清空工作队列 interrupt();       //中断所有的工作线程,该方法继承自ThreadGroup类 } }

/** 等待工作线程把所有任务执行完 */ public void join() { synchronized (this) { isClosed = true; notifyAll();       //唤醒还在getTask()方法中等待任务

的工作线程 }

Thread[] threads = new Thread[activeCount()]; //enumerate()方法继承自ThreadGroup类,获得线程组中当前所有活着的工作线程 int count = enumerate(threads);   for (int i=0; i      try { threads[i].join();     //等待工作线程运行结束 }catch(InterruptedException ex) { } } }

/**  内部类:工作线程  */ private class WorkThread extends Thread { public WorkThread() { //加入到当前ThreadPool线程组中 super(ThreadPool.this,"WorkThread-" + (threadID++)); }

public void run() { while (!isInterrupted()) {  //isInterrupted()方法继承自Thread类,判断线程是否被中断 Runnable task = null; try {       //取出任务 task = getTask(); }catch (InterruptedException ex){}

// 如果getTask()返回null或者线程执行getTask()时被中断,则结束此线程 if (task == null) return; try { //运行任务,异常在catch代码块中捕获 task.run(); } catch (Throwable t) { t.printStackTrace(); } }      //#while }       //#run() }       //#WorkThread类 }

在ThreadPool类中定义了一个LinkedList类型的workQueue成员变量,它表示工作队列,用来存放线程池要执行的任务,每个 任务都是Runnable实例。ThreadPool类的客户程序(利用ThreadPool来执行任务的程序)只要调用ThreadPool类的 execute (Runnable task)方法,就能向线程池提交任务。在ThreadPool类的execute()方法中,先判断线程池是否已 经关闭。如果线程池已经关闭,就不再接收任务,否则就把任务加入到工作队列中,并且唤醒正在等待任务的工作线程。

分享到:
评论

相关推荐

    Java网络编程精解之ServerSocket用法详解

    Java网络编程的核心在于客户端与服务器端的交互,而ServerSocket是Java中用于服务器端的类,它使得服务器能够监听特定端口,接收客户端的连接请求。本文将详细讲解ServerSocket的使用方法及其在多线程环境下的应用。...

    Java网络编程精解(孙卫琴)电子教案

    《Java网络编程精解》是孙卫琴老师的一本经典教程,主要涵盖了Java语言在网络编程领域的深度解析。这本书深入浅出地介绍了如何使用Java进行网络通信,包括基本的TCP/IP协议、套接字编程、多线程技术以及HTTP、FTP等...

    java网络编程(非阻塞与阻塞编程)

    在深入探讨Java网络编程中的非阻塞与阻塞编程之前,我们先来了解这两个概念的基本含义。阻塞编程,通常指的是在程序执行过程中,当某一部分代码遇到I/O操作时,如读写文件或网络通信,整个程序会暂停运行,等待I/O...

    读书笔记:JAVA网络编程精解.zip

    读书笔记:JAVA网络编程精解

    Java网络编程精解PPT课件.ppt

    Java网络编程精解PPT课件.ppt 本资源摘要信息是关于Java网络编程的PPT课件,主要介绍了基于UDP的数据报和套接字的相关知识点。 UDP协议简介 UDP(User Datagram Protocol,用户数据报协议)是一种传输层协议,...

    Java编程案例精解源代码

    Java编程是信息技术领域中最受欢迎的编程语言之一,尤其在企业级应用开发中占据主导地位。"Java编程案例精解源代码"提供了丰富的实例,帮助学习者深入理解和掌握Java编程技术。这个压缩包文件包含了与书本配套的完整...

    读书笔记:孙卫琴《Java网络编程精解》源码.zip

    读书笔记:孙卫琴《Java网络编程精解》源码

    java编程案例精解

    Java编程是计算机科学领域中最广泛使用的编程语言之一,尤其在企业级应用开发中占据着核心地位。本资源“Java编程案例精解”旨在通过实际案例深入解析Java编程的各个方面,帮助学习者掌握Java编程的核心技术和实战...

    Java编程案例精解素材.rar

    本资源“Java编程案例精解素材.rar”包含了一系列实用的Java编程示例,旨在帮助学习者深入理解和掌握Java的核心概念与技术。 1. **使用邮件客户端工具**:JavaMail API是Java中用于处理电子邮件的库,它允许开发者...

    Java编程案例精解

    在《Java编程案例精解》的光盘资料中,可能包含了大量的实战项目,比如简单的命令行应用、图形用户界面(GUI)程序、网络编程案例、多线程编程、数据库连接(JDBC)操作以及I/O流的使用。这些案例能帮助读者在实践中...

    JavaScript 编程精解 中文第三版

    JavaScript 编程精解 中文第三版 JavaScript 编程精解 中文第三版

    java网络精解源码

    1. **Java网络API**: Java提供了一系列的API用于网络编程,包括Socket、ServerSocket、DatagramSocket和DatagramPacket等类。Socket用于实现TCP连接,ServerSocket则用于创建服务器端的监听套接字,而DatagramSocket...

    java网络编程

    "Java网络编程精解-孙卫琴.rar"可能是孙卫琴老师的著作,她是一位知名的Java教育专家。这本书可能详细解析了Java网络编程的各个方面,包括URL类的使用、HTTP协议的实现、NIO(非阻塞I/O)以及高级网络编程技巧。通过...

    java网络编程_孙卫琴_有书签

    《Java网络编程精解》是孙卫琴先生的一本经典著作,主要针对Java开发者,深入讲解了Java在网络编程领域的核心知识。这本书涵盖了从基础概念到高级应用的多个方面,旨在帮助读者理解并掌握Java如何进行网络通信。以下...

    Java编程精解(孙卫琴)

    Java编程精解(孙卫琴) 经典Java书籍,你值得拥有

    Java数据库编程技术精解

    Java数据库编程技术精解,是Java开发者必备的技能之一,主要围绕JDBC(Java Database Connectivity)接口进行深入探讨。JDBC是Java平台中用于与各种数据库进行交互的标准API,无论你是初学者还是经验丰富的开发人员...

    javascript编程精解第三版中文版

    javascript编程精解第三版中文版,来自github的翻译,epub电子书可在手机上看。

    《JavaScript编程精解》.pdf

    文件标题是“《JavaScript编程精解》.pdf”,描述中提到了“JavaScript 编程精解 中文第一版”,而标签同样为“JavaScript 编程精解”。部分内容重复提及了访问“稀酷客”网站的链接,这可能是出版商提供的额外资源...

Global site tag (gtag.js) - Google Analytics