友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第125部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!


    // Shorter default pause than ColorBoxes:  

    int pause = 5;  

    int grid = 8;  

    if(args。length 》 0)   

      pause = Integer。parseInt(args'0');  

    if(args。length 》 1)  

      grid = Integer。parseInt(args'1');  

    Frame f = new ColorBoxes2(pause; grid);  

    f。setSize(500; 400);  

    f。setVisible(true);    

  }  

} ///:~  

  

在ColorBoxes2 中,我们创建了CBoxVector 的一个数组,并对其初始化,使其容下各个CBoxVector 网格。 

每个网格都知道自己该“睡眠”多长的时间。随后为每个 CBoxVector 都添加等量的 Cbox2 对象,而且将每个 

Vector 都告诉给 go(),用它来启动自己的线程。  

CBox2 类似CBox——能用一种随机选择的颜色描绘自己。但那就是 CBox2 能够做的全部工作。所有涉及线程 

的处理都已移至CBoxVector 进行。  

CBoxVector 也可以拥有继承的 Thread,并有一个类型为Vector 的成员对象。这样设计的好处就是 

addElement()和 elementAt()方法可以获得特定的参数以及返回值类型,而不是只能获得常规 Object (它们 

的名字也可以变得更短)。然而,这里采用的设计表面上看需要较少的代码。除此以外,它会自动保留一个 

Vector 的其他所有行为。由于 elementAt()需要大量进行“封闭”工作,用到许多括号,所以随着代码主体 

的扩充,最终仍有可能需要大量代码。  

和以前一样,在我们实现Runnable 的时候,并没有获得与 Thread 配套提供的所有功能,所以必须创建一个 

新的Thread,并将自己传递给它的构建器,以便正式“启动”——start()——一些东西。大家在 

CBoxVector 构建器和 go()里都可以体会到这一点。run()方法简单地选择Vector 里的一个随机元素编号,并 

为那个元素调用nextColor(),令其挑选一种新的随机颜色。  

运行这个程序时,大家会发现它确实变得更快,响应也更迅速(比如在中断它的时候,它能更快地停下 

来)。而且随着网格尺寸的壮大,它也不会经常性地陷于“停顿”状态。因此,线程的处理又多了一项新的 

考虑因素:必须随时检查自己有没有“太多的线程”(无论对什么程序和运行平台)。若线程太多,必须试 

着使用上面介绍的技术,对程序中的线程数量进行“平衡”。如果在一个多线程的程序中遇到了性能上的问 

题,那么现在有许多因素需要检查:  

(1) 对 sleep,yield()以及/或者wait()的调用足够多吗?  

(2) sleep()的调用时间足够长吗?  

(3) 运行的线程数是不是太多?  

(4) 试过不同的平台和 JVM 吗?  

象这样的一些问题是造成多线程应用程序的编制成为一种“技术活”的原因之一。  



                                                                                      534 


…………………………………………………………Page 536……………………………………………………………

14。6 总结  



何时使用多线程技术,以及何时避免用它,这是我们需要掌握的重要课题。骼它的主要目的是对大量任务进 

行有序的管理。通过多个任务的混合使用,可以更有效地利用计算机资源,或者对用户来说显得更方便。资 

源均衡的经典问题是在 IO等候期间如何利用 CPU。至于用户方面的方便性,最经典的问题就是如何在一个长 

时间的下载过程中监视并灵敏地反应一个“停止”(stop )按钮的按下。  

多线程的主要缺点包括:  

(1) 等候使用共享资源时造成程序的运行速度变慢。  

(2) 对线程进行管理要求的额外CPU 开销。  

(3) 复杂程度无意义的加大,比如用独立的线程来更新数组内每个元素的愚蠢主意。  

(4) 漫长的等待、浪费精力的资源竞争以及死锁等多线程症状。  

线程另一个优点是它们用“轻度”执行切换(100条指令的顺序)取代了“重度”进程场景切换(1000 条指 

令)。由于一个进程内的所有线程共享相同的内存空间,所以“轻度”场景切换只改变程序的执行和本地变 

量。而在“重度”场景切换时,一个进程的改变要求必须完整地交换内存空间。  

线程处理看来好象进入了一个全新的领域,似乎要求我们学习一种全新的程序设计语言——或者至少学习一 

系列新的语言概念。由于大多数微机操作系统都提供了对线程的支持,所以程序设计语言或者库里也出现了 

对线程的扩展。不管在什么情况下,涉及线程的程序设计:  

(1) 刚开始会让人摸不着头脑,要求改换我们传统的编程思路;  

(2) 其他语言对线程的支持看来是类似的。所以一旦掌握了线程的概念,在其他环境也不会有太大的困难。 

尽管对线程的支持使Java 语言的复杂程度多少有些增加,但请不要责怪 Java 。毕竟,利用线程可以做许多 

有益的事情。  

多个线程可能共享同一个资源(比如一个对象里的内存),这是运用线程时面临的最大的一个麻烦。必须保 

证多个线程不会同时试图读取和修改那个资源。这要求技巧性地运用 synchronized (同步)关键字。它是一 

个有用的工具,但必须真正掌握它,因为假若操作不当,极易出现死锁。  

除此以外,运用线程时还要注意一个非常特殊的问题。由于根据Java 的设计,它允许我们根据需要创建任意 

数量的线程——至少理论上如此(例如,假设为一项工程方面的有限元素分析创建数以百万的线程,这对 

Java 来说并非实际)。然而,我们一般都要控制自己创建的线程数量的上限。因为在某些情况下,大量线程 

会将场面变得一团糟,所以工作都会几乎陷于停顿。临界点并不象对象那样可以达到几千个,而是在 100 以 

下。一般情况下,我们只创建少数几个关键线程,用它们解决某个特定的问题。这时数量的限制问题不大。 

但在较常规的一些设计中,这一限制确实会使我们感到束手束脚。  

大家要注意线程处理中一个不是十分直观的问题。由于采用了线程“调度”机制,所以通过在run()的主循 

环中插入对 sleep()的调用,一般都可以使自己的程序运行得更快一些。这使它对编程技巧的要求非常高, 

特别是在更长的延迟似乎反而能提高性能的时候。当然,之所以会出现这种情况,是由于在正在运行的线程 

准备进入“休眠”状态之前,较短的延迟可能造成“sleep()结束”调度机制的中断。这便强迫调度机制将其 

中止,并于稍后重新启动,以便它能做完自己的事情,再进入休眠状态。必须多想一想,才能意识到事情真 

正的麻烦程度。  

本章遗漏的一件事情是一个动画例子,这是目前程序片最流行的一种应用。然而,Java JDK 配套提供了解决 

这个问题的一整套方案(并可播放声音),大家可到java。sun。 的演示区域下载。此外,我们完全有理由 

相信未来版本的Java 会提供更好的动画支持——尽管目前的 Web 涌现出了与传统方式完全不同的非 Java、 

非程序化的许多动画方案。如果想系统学习Java 动画的工作原理,可参考《Core Java——核心Java 》一 

书,由Corn ell&Horstmann 编著,Prentice …Hall 于 1997 年出版。若欲更深入地了解线程处理,请参考 

 《Concurrent Programming in Java——Java 中的并发编程》,由Doug Lea 编著,Addison…Wiseley 于 

1997 年出版;或者《Java Threads——Java 线程》,Oaks&Wong 编著,O'Reilly 于 1997 年出版。  



14。7 练习  



(1) 从Thread 继承一个类,并(过载)覆盖 run()方法。在run()内,打印出一条消息,然后调用 

sleep()。重复三遍这些操作,然后从run()返回。在构建器中放置一条启动消息,并覆盖 finalize(),打印 

一条关闭消息。创建一个独立的线程类,使它在run()内调用System。gc()和 System。runFinalization(), 

并打印一条消息,表明调用成功。创建这两种类型的几个线程,然后运行它们,看看会发生什么。  

(2) 修改Counter2。java ,使线程成为一个内部类,而且不需要明确保存指向Counter2 的一个。  

(3) 修改Sharing2。java,在TwoCounter 的run()方法内部添加一个synchronized (同步)块,而不是同步 

整个run()方法。  



                                                               535 


…………………………………………………………Page 537……………………………………………………………

(4) 创建两个Thread 子类,第一个的run()方法用于最开始的启动,并捕获第二个Thread 对象的句柄,然 

后调用wait()。第二个类的run()应在过几秒后为第一个线程调用modifyAll(),使第一个线程能打印出一 

条消息。  

(5) 在Ticker2 内的Counter5。java 中,删除yield(),并解释一下结果。用一个sleep()换掉yield(),再 

解释一下结果。  

(6) 在ThreadGroup1。java 中,将对sys。suspend()的调用换成对线程组的一个wait()调用,令其等候2 秒 

钟。为了保证获得正确的结果,必须在一个同步块内取得 sys 的对象锁。  

(7) 修改Daemons。java,使main()有一个 sleep(),而不是一个readLine()。实验不同的睡眠时间,看看会 

有什么发生。  

(8) 到第7 章(中间部分)找到那个GreenhouseControls。java 例子,它应该由三个文件构成。在 

Event。java 中,Event 类建立在对时间的监视基础上。修改这个 Event,使其成为一个线程。然后修改其余 

的设计,使它们能与新的、以线程为基础的Event 正常协作。  



                                                                       536 


…………………………………………………………Page 538……………………………………………………………

                           第 15 章  网络编程  



  

历史上的网络编程都倾向于困难、复杂,而且极易出错。  

程序员必须掌握与网络有关的大量细节,有时甚至要对硬件有深刻的认识。一般地,我们需要理解连网协议 

中不同的“层”(Layer)。而且对于每个连网库,一般都包含了数量众多的函数,分别涉及信息块的连接、 

打包和拆包;这些块的来回运输;以及握手等等。这是一项令人痛苦的工作。  

但是,连网本身的概念并不是很难。我们想获得位于其他地方某台机器上的信息,并把它们移到这儿;或者 

相反。这与读写文件非常相似,只是文件存在于远程机器上,而且远程机器有权决定如何处理我们请求或者 

发送的数据。  

Java 最出色的一个地方就是它的“无痛苦连网”概念。有关连网的基层细节已被尽可能地提取出去,并隐藏 

在JVM 以及Java 的本机安装系统里进行控制。我们使用的编程模型是一个文件的模型;事实上,网络连接 

 (一个“套接字”)已被封装到系统对象里,所以可象对其他数据流那样采用同样的方法调用。除此以外, 

在我们处理另一个连网问题——同时控制多个网络连接——的时候,Java 内建的多线程机制也是十分方便 

的。  

本章将用一系列易懂的例子解释Java 的连网支持。  



15。1 机器的标识  



当然,为了分辨来自别处的一台机器,以及为了保证自己连接的是希望的那台机器,必须有一种机制能独一 

无二地标识出网络内的每台机器。早期网络只解决了如何在本地网络环境中为机器提供唯一的名字。但 Java 

面向的是整个因特网,这要求用一种机制对来自世界各地的机器进行标识。为达到这个目的,我们采用了 IP 

 (互联网地址)的概念。IP 以两种形式存在着:  

(1) 大家最熟悉的DNS (域名服务)形式。我自己的域名是bruceeckel。。所以假定我在自己的域内有一 

台名为Opus 的计算机,它的域名就可以是 Opus。bruceeckel。。这正是大家向其他人发送电子函件时采用 

的名字,而且通常集成到一个万维网(WWW)地址里。  

(2) 此外,亦可采用“四点”格式,亦即由点号(。)分隔的四组数字,比如202。98。32。111 。  

不管哪种情况,IP地址在内部都表达成一个由32 个二进制位(bit)构成的数字(注释①),所以IP地址 

的每一组数字都不能超过255。利用由java 提供的 static InetAddress。getByName(),我们可以让一个 

特定的 Java 对象表达上述任何一种形式的数字。结果是类型为InetAddress 的一个对象,可用它构成一个 

 “套接字”(Socket),大家在后面会见到这一点。  

  

①:这意味着最多只能得到40 亿左右的数字组合,全世界的人很快就会把它用光。但根据目前正在研究的新 

IP编址方案,它将采用 128 bit 的数字,这样得到的唯一性IP地址也许在几百年的时间里都不会用完。  

  

作为运用 InetAddress。getByName()一个简单的例子,请考虑假设自己有一家拨号连接因特网服务提供者 

 (ISP),那么会发生什么情况。每次拨号连接的时候,都会分配得到一个临时 IP地址。但在连接期间,那 

个 IP 地址拥有与因特网上其他 IP 地址一样的有效性。如果有人按照你的 IP地址连接你的机器,他们就有可 

能使用在你机器上运行的Web 或者 FTP 服务器程序。当然这有个前提,对方必须准确地知道你目前分配到的 

IP。由于每次拨号连接获得的IP 都是随机的,怎样才能准确地掌握你的IP 呢?  

下面这个程序利用 InetAddress。getByName()来产生你的 IP 地址。为了让它运行起来,事先必须知道计算机 

的名字。该程序只在Windows 95 中进行了测试,但大家可以依次进入自己的 “开始”、“设置”、“控制面 

板”、“网络”,然后进入“标识”卡片。其中,“计算机名称”就是应在命令行输入的内容。  

  

//: WhoAmI。java  

// Finds out your network address when you're   

// connected to the Internet。  

package c15;  

import java。*;  

  

public class WhoAmI {  

  public static void main(String'' args)   



                                                                        537 


…………………………………………………………Page 539……………………………………………………………

      throws Exception {  

    if(args。length != 1) {  

      System。err。println(  

        〃Usage: WhoAmI MachineName〃);  

      System。exit(1);  

    }  

    InetAddress a =   

      InetAddress。getByName(args'0');  

    System。
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!