多任务处理:多线程

我们在前面所介绍的基本TCP响应服务器一次只能处理一个客户端的请求。当一个客户端向一个已经被其他客户端占用的服务器发送连接请求时,虽然其在连接建立后即可向服务器端发送数据,服务器端在处理完已有客户端的请求前,却不会对新的客户端作出响应,。这种类型的服务器称为"迭代服务器(iterative server"。迭代服务器按顺序处理客户端的请求,也就是说在完成了对前一客户端的服务后,才会对下一个客户端进行响应。这种服务器最适用于每个客户端所请求的连接时间都被限制在较小范围内的应用中,而对于允许客户端请求长时间服务的情况,后续客户端将面临无法接受的长时间等待。

 为了更直观地说明这种情况,我们在TCPEchoClient.java文件中调用Socket构造器的代码段后面加入Thread.sleep()方法来实现10秒的暂停,并实验多个客户端同时访问TCP响应服务器的情况。   这里的sleep调用是用来模拟一个客户端长时间占用服务器的情况,如慢速的文件或网络I/O(输入输出)。通过实验可以看到,一个新的客户端必须等到服务器对前面所有已连接的客户端完成服务后,才能获得服务器的对它的请求的响应。

 我们需要一种方法可以独立处理每一个连接,并使它们不会产生相互干扰,而Java多线程技术刚好满足了这一需求,这一机制使服务器能够方便地同时处理多个客户端的请求。通过使用多线程,一个应用程序可以并行执行多项任务,就好像有多个Java虚拟机在同时运行。(实际上是多个线程共享了同一个Java虚拟机。)在我们的响应服务器中,可以为每个客户端分配一个执行线程来实现。到目前为止,我们所看到的全部例子都是由一个简单执行main()方法的单线程组成的。

 本节我们将介绍两种实现并行服务器(concurrent servers)的编程方法,分别为:一客户一线程(thread-per-client),即为每一个客户端连接创建一个新的线程;线程池(threadpool),即将客户端连接分配给一组事先创建好的线程。我们还会对Java中能够简化实现多线程服务器的内置工具进行描述。

 

Java      多线程

 Java提供了两种在一个新线程中执行任务的方法:1)为Thread类定义一个带有run()方法的子类,在run()方法中包含要执行的任务,并实例化这个子类;或2)定义一个实现了Runnable接口的类,并在run()方法中包含要执行的任务,再将这个类的一个实例传递给Thread的构造函数。无论哪种情况,新线程创建后并不立即执行,而是要等到其start()方法被调用。第一种方法只适用于没有继承于其他类的类,因此我们专注于第二种方法,它能够适用于多种情况。Runnable接口中只包含一个方法原型:

interface Runnable {

void run();

}

 Thread对象的start()方法被调用时,Java虚拟机将在一个新的线程中执行该对象的run()方法,从而实现与其他任务并行执行。同时,原来的线程从调用start()方法的地方返回,继续独立执行。(值得注意到是,直接调用run()方法并不产生新线程,而只会像其它类方法一样在调用者线程中执行)由于每个线程的run()方法是以任意的方式交错声明的,因此无法准确预测各个线程的执行顺序。

 在下面的例子中,ThreadExample.java实现了Runnable接口,并在run()方法中反复向系统输出流打印一句问候语。 

ThreadExample.java

0  import java.util.concurrent.TimeUnit;

1

2  public class ThreadExample implements Runnable {

3

4   private String greeting; // Message to print to console

5

6  public ThreadExample(String greeting) {

7   this.greeting = greeting;

8  }

9

10 public void run() {

11  while (true) {

12   System.out.println(Thread.currentThread().getName()

+ ": " + greeting);

13  try {

14  // Sleep 0 to 100 milliseconds

15  TimeUnit.MILLISECONDS.sleep(((long) Math.random() *

100));

16  } catch (InterruptedException e) {

17 } // Should not happen

18 }

19 }

20

21 public static void main(String[] args) {

22  new Thread(new ThreadExample("Hello")).start();

23  new Thread(new ThreadExample("Aloha")).start();

24  new Thread(new ThreadExample("Ciao")).start();

25 }

26 }

 

ThreadExample.java

1.实现Runnable接口的声明:第2 

ThreadExample实现了Runnable接口,因此可以将其传递给Thread类的构造函数。如ThreadExample没有提供run()方法,编译器将会给出警告。 

2.类成员变量与构造函数:第4-8 

每个ThreadExample类的实例都包含了自己的问候语句,存放在类实例的字符串成员变量中。

3.run()方法:第10-19

循环语句将反复执行以下内容:

打印出线程名和当前实例问候语句:第12

静态方法Thread.currentThread()将从调用它的线程中返回一个该线程的引用,getName()方法返回一个包含该线程名单字符串。 

暂停线程:第13-17 

每个线程实例在打印出问候信息后,都将随机暂停一定的时间(在0100毫秒之间),这通过把暂停的毫秒数作为参数传递给Thread.sleep()静态方法来实现。Math.random()方法返回0.01.0之间的一个double型随机数。Thread.sleep()方法可以被其他线程打断,并抛InterruptedException异常。本例中没有包含打断该方法的调用,因此这个应用中不会发生这种异常。

4.main()方法:第21-25 

main()方法中的三行声明都完成了如下工作:1)用不同的问候语字符串创建了ThreadExample类的一个新实例,2)将这个新实例传递给Thread类的构造函数,3)调用新Thread实例的start()方法。在主线程已经结束时,每个线程正在独立地执行ThreadExample类的run()方法。注意,Java虚拟机只有在所有非守护线程(见Thread API)都执行完毕的情况下才终止。 

在程序运行的时候,三条问候语句将交错地打印到控制台中。很多因素都会对线程真实的执行顺序产生影响,而这些因素是用户无法观察到的。对于我们例子中的这种服务器端,每个客户端的执行过程都与其他客户端相互独立,这就非常适合使用多线程技术来实现。但是,如果客户端的执行过程涉及到需要更新服务器端线程间的共享信息,这将变得相当麻烦。在这种情况下,必须非常小心,以确保不同的线程间在共享数据上得到了妥善的同步,否则,会导致共享信息不一致的状况发生,更麻烦的是这些问题追踪起来还非常困难。如果要完整地介绍并发技术和工具需要一整本书的篇幅,例如Goetz等人就写了一本非常好的著作。

 

相关下载:

Java_TCPIP_Socket编程(doc)

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

 

文献来源:

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

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

 

原文地址:https://www.cnblogs.com/wuyida/p/6301072.html