|
![]() |
名片设计 CorelDRAW Illustrator AuotoCAD Painter 其他软件 Photoshop Fireworks Flash |
|
Java 编程语言的线程模型可能是此语言中最薄弱的部分。它完全不适合实际复杂程序的要求,而且也完全不是面向对象的。本文建议对 Java 语言进行重大修改和补充,以解决这些问题。 Java 语言的线程模型是此语言的一个最难另人满足的部分。尽管 Java 语言本身就支持线程编程是件好事,但是它对线程的语法和类包的支持太少,只能适用于极小型的应用环境。 关于 Java 线程编程的大多数书籍都长篇累牍地指出了 Java 线程模型的缺陷,并提供了解决这些问题的急救包(Band-Aid/邦迪创可贴)类库。我称这些类为急救包,是因为它们所能解决的问题本应是由 Java 语言本身语法所包含的。从长远来看,以语法而不是类库方式,将能产生更高效的代码。这是因为编译器和 Java 虚拟器 (JVM) 能一同优化程序代码,而这些优化对于类库中的代码是很难或无法实现的。 Allen Holub 指出,在我的《Taming Java Threads 》(请参阅 参考资料 )书中以及本文中,我进一步建议对 Java 编程语言本身进行一些修改,以使得它能够真正解决这些线程编程的问题。本文和我这本书的主要区别是,我在撰写本文时进行了更多的思索, 所以对书中的提议加以了提高。这些建议只是尝试性的 -- 只是我个人对这些问题的想法,而且实现这些想法需要进行大量的工作以及同行们的评价。但这是究竟是一个开端,我有意为解决这些问题成立一个专门的工作组,假如您感爱好,请发 e-mail 到 threading@holub.com 。一旦我真正着手进行,我就会给您发通知。 这里提出的建议是异常大胆的。有些人建议对 Java 语言规范 (JLS)(请参阅参考资料 )进行细微和少量的修改以解决当前模糊的 JVM 行为,但是我却想对其进行更为彻底的改进。 在实际草稿中,我的许多建议包括为此语言引入新的要害字。虽然通常要求不要突破一个语言的现有代码是准确的,但是假如该语言的并不是要保持不变以至于过时的话,它就必须能引入新的要害字。为了使引入的要害字与现有的标识符不产生冲突,经过细心考虑,我将使用一个 ($) 字符,而这个字符在现有的标识符中是非法的。(例如,使用 $task,而不是 task)。此时需要编译器的命令行开关提供支持,能使用这些要害字的变体,而不是忽略这个美元符号。 task(任务)的概念 Java 线程模型的根本问题是它完全不是面向对象的。面向对象 (OO) 设计人员根本不按线程角度考虑问题;他们考虑的是同步 信息 异步 信息(同步信息被立刻处理 -- 直到信息处理完成才返回消息句柄;异步信息收到后将在后台处理一段时间 -- 而早在信息处理结束前就返回消息句柄)。Java 编程语言中的 Toolkit.getImage() 方式就是异步信息的一个好例子。 getImage() 的消息句柄将被立刻返回,而不必等到整个图像被后台线程取回。 这是面向对象 (OO) 的处理方式。但是,如前所述,Java 的线程模型是非面向对象的。一个 Java 编程语言线程实际上只是一个run() 过程,它调用了其它的过程。在这里就根本没有对象、异步或同步信息以及其它概念。 对于此问题,在我的书中深入讨论过的一个解决方式是,使用一个Active_object。 active 对象是可以接收异步哀求的对象,它在接收到哀求后的一段时间内以后台方法得以处理。在 Java 编程语言中,一个哀求可被封装在一个对象中。例如,你可以把一个通过 Runnable 接口实现的实例传送给此 active 对象,该接口的 run() 方式封装了需要完成的工作。该 runnable 对象被此 active 对象排入到队列中,当轮到它执行时,active 对象使用一个后台线程来执行它。 在一个 active 对象上运行的异步信息实际上是同步的,因为它们被一个单一的服务线程按顺序从队列中取出并执行。因此,使用一个 active 对象以一种更为过程化的模型可以消除大多数的同步问题。 在某种意义上,Java 编程语言的整个 Swing/AWT 子系统是一个 active 对象。向一个 Swing 队列传送一条讯息的唯一安全的途径是,调用一个类似SwingUtilities.invokeLater() 的方式,这样就在 Swing 事件队列上发送了一个 runnable 对象,当轮到它执行时, Swing 事件处理线程将会处理它。 那么我的第一个建议是,向 Java 编程语言中加入一个task (任务)的概念,从而将active 对象集成到语言中。( task的概念是从 Intel 的 RMX 操作系统和 Ada 编程语言借鉴过来的。大多数实时操作系统都支持类似的概念。) 一个任务有一个内置的 active 对象分发程序,并自动治理那些处理异步信息的全部机制。 定义一个任务和定义一个类基本一样,不同的只是需要在任务的方式前加一个asynchronous 修饰符来指示 active 对象的分配程序在后台处理这些方式。 所有的写哀求都用一个dispatch() 过程调用被放在 active-object 的输入队列中排队。在后台处理这些异步信息时出现的任何非常 (exception) 都由 Exception_handler 对象处理,此 Exception_handler 对象被传送到 File_io_task 的构造函数中。 这种基于类的处理方式,其主要问题是太复杂了 -- 对于一个这样简朴的操作,代码太杂了。向 Java 语言引入$task 和 $asynchronous 要害字后,就可以按下面这样重写以前的代码: 注重,异步方式并没有指定返回值,因为其句柄将被立刻返回,而不用等到哀求的操作处理完成后。所以,此时没有合理的返回值。对于派生出的模型,$task 要害字和 class 相同同效: $task 可以实现接口、继续类和继续的其它任务。标有 asynchronous 要害字的方式由 $task 在后台处理。其它的方式将同步运行,就像在类中相同。 $task要害字可以用一个可选的 $error 从句修饰 (如上所示),它表明对任何无法被异步方式本身捕获的非常将有一个缺省的处理程序。我使用 $ 来代表被抛出的非常对象。假如没有指定 $error 从句,就将打印出一个合理的出错信息(很可能是堆栈跟踪信息)。 注重,为确保线程安全,异步方式的参数必须是不变 (immutable) 的。运行时系统应通过相关语义来保证这种不变性(简朴的复制通常是不够的)。 所有的 task 对象必须支持一些伪信息 (pseudo-message)。 除了常用的修饰符(public 等), task 要害字还应接受一个 $pooled(n) 修饰符,它导致 task 使用一个线程池,而不是使用单个线程来运行异步哀求。 n 指定了所需线程池的大小;必要时,此线程池可以增加,但是当不再需要线程时,它应该缩到原来的大小。伪域 (pseudo-field) $pool_size 返回在 $pooled(n) 中指定的原始 n 参数值。 在《Taming Java Threads 》的第八章中,我给出了一个服务器端的 socket 处理程序,作为线程池的例子。它是关于使用线程池的任务的一个好例子。其基本思路是产生一个独立对象,它的任务是监控一个服务器端的 socket。每当一个客户机连接到服务器时,服务器端的对象会从池中抓取一个预先创建的睡眠线程,并把此线程设置为服务于客户端连接。socket 服务器会产出一个额外的客户服务线程,但是当连接关闭时,这些额外的线程将被删除。 Socket_server对象使用一个独立的后台线程处理异步的 listen() 哀求,它封装 socket 的“接受”循环。当每个客户端连接时, listen() 哀求一个 Client_handler 通过调用 handle() 来处理哀求。每个 handle() 哀求在它们自己的线程中执行(因为这是一个 $pooled 任务)。 注重,每个传送到$pooled $task 的异步消息实际上都使用它们自己的线程来处理。典型情况下,由于一个 $pooled $task 用于实现一个自主操作;所以对于解决与访问状态变量有关的潜在的同步问题,最好的解决方式是在 $asynchronous 方式中使用 this 是指向的对象的一个独有副本。这就是说,当向一个 $pooled $task 发送一个异步哀求时,将执行一个 clone() 操作,并且此方式的 this 指针会指向此克隆对象。线程之间的通信可通过对 static 区的同步访问实现。 改进synchronized 虽然在多数情况下,$task 消除了同步操作的要求,但是不是所有的多线程系统都用任务来实现。所以,还需要改进现有的线程模块。 synchronized 要害字有下列缺点: 无法指定一个超时值。 无法中断一个正在等待哀求锁的线程。 无法安全地哀求多个锁 。(多个锁只能以依次序获得。) 解决这些问题的办法是:扩展synchronized 的语法,使它支持多个参数和能接受一个超时说明(在下面的括弧中指定)。下面是我希望的语法: synchronized(x && y && z) 获得 x、y 和 z 对象的锁。 synchronized(x || y || z) 获得 x、y 或 z 对象的锁。 synchronized( (x && y ) || z) 对于前面代码的一些扩展。 synchronized(...)[1000] 设置 1 秒超时以获得一个锁。 synchronized[1000] f(){...} 在进入 f() 函数时获得 this 的锁,但可有 1 秒超时。 TimeoutException是 RuntimeException 派生类,它在等待超时后即被抛出。 超时是需要的,但还不足以使代码强壮。您还需要具备从外部中止哀求锁等待的能力。所以,当向一个等待锁的线程传送一个interrupt() 方式后,此方式应抛出一个 SynchronizationException 对象,并中断等待的线程。这个非常应是 RuntimeException 的一个派生类,这样不必特殊处理它。 对synchronized 语法这些推荐的更改方式的主要问题是,它们需要在二进制代码级上修改。而目前这些代码使用进入监控(enter-monitor)和退出监控(exit-monitor)指令来实现 synchronized 。而这些指令没有参数,所以需要扩展二进制代码的定义以支持多个锁定哀求。但是这种修改不会比在 Java 2 中修改 Java 虚拟机的更轻松,但它是向下兼容现存的 Java 代码。 另一个可解决的问题是最常见的死锁情况,在这种情况下,两个线程都在等待对方完成某个操作。 设想一个线程调用a() ,但在获得 lock1 之后在获得 lock2 之前被剥夺运行权。 第二个线程进入运行,调用 b() ,获得了 lock2 ,但是由于第一个线程占用 lock1 ,所以它无法获得 lock1 ,所以它随后处于等待状态。此时第一个线程被唤醒,它试图获得 lock2 ,但是由于被第二个线程占据,所以无法获得。此时出现死锁。 编译器(或虚拟机)会重新排列哀求锁的顺序,使lock1 总是被首先获得,这就消除了死锁。 但是,这种方式对多线程不一定总成功,所以得提供一些方式来自动打破死锁。一个简朴的办法就是在等待第二个锁时常释放已获得的锁。 假如等待锁的每个程序使用不同的超时值,就可打破死锁而其中一个线程就可运行。我建议用以下的语法来取代前面的代码: synchronized语句将永远等待,但是它时常会放弃已获得的锁以打破潜在的死锁可能。在理想情况下,每个重复等待的超时值比前一个相差一随机值。 改进wait() 和 notify() wait()/ notify() 系统也有一些问题: 无法检测 wait() 是正常返回还是因超时返回。 无法使用传统条件变量来实现处于一个“信号”(signaled)状态。 太轻易发生嵌套的监控(monitor)锁定。 超时检测问题可以通过重新定义wait() 使它返回一个 boolean 变量 (而不是 void ) 来解决。一个 true 返回值指示一个正常返回,而 false 指示因超时返回。 基于状态的条件变量的概念是很重要的。假如此变量被设置成false 状态,那么等待的线程将要被阻断,直到此变量进入 true 状态;任何等待 true 的条件变量的等待线程会被自动释放。 (在这种情况下, wait() 调用不会发生阻断。)。 嵌套监控锁定问题异常麻烦,我并没有简朴的解决办法。嵌套监控锁定是一种死锁形式,当某个锁的占有线程在挂起其自身之前不释放锁时,会发生这种嵌套监控封锁。 此例中,在get() 和 put() 操作中涉及两个锁:一个在 Stack 对象上,另一个在 LinkedList 对象上。下面我们考虑当一个线程试图调用一个空栈的 pop() 操作时的情况。此线程获得这两个锁,然后调用 wait() 释放 Stack 对象上 的锁,但是没有释放在 list 上的锁。假如此时第二个线程试图向堆栈中压入一个对象,它会在 synchronized(list) 语句上永远挂起,而且永远不会被答应压入一个对象。由于第一个线程等待的是一个非空栈,这样就会发生死锁。这就是说,第一个线程永远无法从 wait() 返回,因为由于它占据着锁,而导致第二个线程永远无法运行到 notify() 语句。 在这个例子中,有很多明显的办法来解决问题:例如,对任何的方式都使用同步。但是在真实世界中,解决方式通常不是这么简朴。 一个可行的方式是,在wait() 中按照反顺序释放当前线程获取的 所有 锁,然后当等待条件满意后,重新按原始获取顺序取得它们。但是,我能想象出利用这种方法的代码对于人们来说简直无法理解,所以我认为它不是一个真正可行的方式。假如您有好的方式,请给我发 e-mail。 我也希望能等到下述复杂条件被实现的一天。例如: 其中a 、 b 和 c 是任意对象。 修改Thread 类 同时支持抢占式和协作式线程的能力在某些服务器应用程序中是基本要求,尤其是在想使系统达到最高性能的情况下。我认为 Java 编程语言在简化线程模型上走得太远了,并且 Java 编程语言应支持 Posix/Solaris 的“绿色(green)线程”和“轻巧(lightweight)进程”概念(在“(Taming Java Threads ”第一章中讨论)。 这就是说,有些 Java 虚拟机的实现(例如在 NT 上的 Java 虚拟机)应在其内部仿真协作式进程,其它 Java 虚拟机应仿真抢占式线程。而且向 Java 虚拟机加入这些扩展是很轻易的。 一个 Java 的Thread 应始终是抢占式的。这就是说,一个 Java 编程语言的线程应像 Solaris 的轻巧进程相同工作。 Runnable 接口可以用于定义一个 Solaris 式的“绿色线程”,此线程必需能把控制权转给运行在一样轻巧进程中的其它绿色线程。 能有效地为Runnable 对象产生一个绿色线程,并把它绑定到由 Thread 对象代表的轻巧进程中。这种实现对于现有代码是透明的,因为它的有效性和现有的完全相同。 把Runnable 对象想成为绿色线程,使用这种方式,只需向 Thread 的构造函数传递几个 Runnable 对象,就可以扩展 Java 编程语言的现有语法,以支持在一个单一轻巧线程有多个绿色线程。(绿色线程之间可以相互协作,但是它们可被运行在其它轻巧进程 ( Thread 对象) 上的绿色进程( Runnable 对象) 抢占。)。例如,下面的代码会为每个 runnable 对象创建一个绿色线程,这些绿色线程会共享由 Thread 对象代表的轻巧进程。 现有的覆盖(override)Thread 对象并实现 run() 的习惯继承有效,但是它应映射到一个被绑定到一轻巧进程的绿色线程。(在 Thread() 类中的缺省 run() 方式会在内部有效地创建第二个 Runnable 对象。) 线程间的协作 应在语言中加入更多的功能以支持线程间的相互通信。目前,PipedInputStream 和 PipedOutputStream 类可用于这个目的。但是对于大多数应用程序,它们太弱了。我建议向 Thread 类加入下列函数: 增加一个 wait_for_start() 方式,它通常处于阻塞状态,直到一个线程的 run() 方式启动。(假如等待的线程在调用 run 之前被释放,这没有什么问题)。用这种方式,一个线程可以创建一个或多个辅助线程,并保证在创建线程继承执行操作之前,这些辅助线程会处于运行状态。 (向 Object 类)增加 $send (Object o) 和 Object=$receive() 方式,它们将使用一个内部阻断队列在线程之间传送对象。阻断队列应作为第一个 $send() 调用的副产品被自动创建。 $send() 调用会把对象加入队列。 $receive() 调用通常处于阻塞状态,直到有一个对象被加入队列,然后它返回此对象。这种方式中的变量应支持设定入队和出队的操作超时能力: $send (Object o, long timeout) 和 $receive (long timeout)。 对于读写锁的内部支持 读写锁的概念应内置到 Java 编程语言中。读写器锁在“Taming Java Threads ”(和其它地方)中有具体讨论,概括地说:一个读写锁支持多个线程同时访问一个对象,但是在同一时刻只有一个线程可以修改此对象,并且在访问进行时不能修改。 对于一个对象,应该只有在$writing 块中没有线程时,才支持多个线程进入 $reading 块。在进行读操作时,一个试图进入 $writing 块的线程会被阻断,直到读线程退出 $reading 块。 当有其它线程处于 $writing 块时,试图进入 $reading 或 $writing 块的线程会被阻断,直到此写线程退出 $writing 块。 假如读和写线程都在等待,缺省情况下,读线程会首先进行。但是,可以使用$writer_priority 属性修改类的定义来改变这种缺省方法。 访问部分创建的对象应是非法的 当前情况下,JLS 答应访问部分创建的对象。例如,在一个构造函数中创建的线程可以访问正被创建的对象,既使此对象没有完全被创建。 设置x 为 -1 的线程可以和设置 x 为 0 的线程同时进行。所以,此时 x 的值无法猜测。 对此问题的一个解决方式是,在构造函数没有返回之前,对于在此构造函数中创建的线程,既使它的优先级比调用new 的线程高,也要禁止运行它的 run() 方式。 这就是说,在构造函数返回之前,start() 哀求必须被推迟。 另外,Java 编程语言应可答应构造函数的同步。换句话说,下面的代码(在当前情况下是非法的)会象预期的那样工作: 我认为第一种方式比第二种更简洁,但实现起来更为困难。 volatile要害字应象预期的那样工作 JLS 要求保留对于 volatile 操作的哀求。大多数 Java 虚拟机都简朴地忽略了这部分内容,这是不应该的。在多处理器的情况下,许多主机都出现了这种问题,但是它本应由 JLS 加以解决的。假如您对这方面感爱好,马里兰大学的 Bill Pugh 正在致力于这项工作(请参阅参考资料 )。 访问的问题 假如缺少良好的访问控制,会使线程编程异常困难。大多数情况下,假如能保证线程只从同步子系统中调用,不必考虑线程安全(threadsafe)问题。我建议对 Java 编程语言的访问权限概念做如下限制;应精确使用 package 要害字来限制包访问权。我认为当缺省行为的存在是任何一种计算机语言的一个瑕疵,我对现在存在这种缺省权限感到很疑惑(而且这种缺省是“包(package)”级别的而不是“私有(private)”)。在其它方面,Java 编程语言都不提供等同的缺省要害字。虽然使用显式的 package 的限定词会破坏现有代码,但是它将使代码的可读性更强,并能消除整个类的潜在错误 (例如,假如访问权是由于错误被忽略,而不是被故意忽略)。 重新引入 private protected ,它的功能应和现在的 protected 相同,但是不应答应包级别的访问。 答应 private private 语法指定“实现的访问”对于所有外部对象是私有的,甚至是当前对象是的同一个类的。对于“.”左边的唯一引用(隐式或显式)应是 this 。 扩展 public 的语法,以授权它可制定特定类的访问。例如,下面的代码应答应 Fred 类的对象可调用 some_method() ,但是对其它类的对象,这个方式应是私有的。 这种建议不同于 C++ 的 "friend" 机制。 在 "friend" 机制中,它授权一个类访问另一个类的所有 私有部分。在这里,我建议对有限的方式集合进行严格控制的访问。用这种方式,一个类可以为另一个类定义一个接口,而这个接口对系统的其余类是不可见的。 除非域引用的是真正不变(immutable)的对象或static final 基本类型,否则所有域的定义应是 private 。对于一个类中域的直接访问违背了 OO 设计的两个基本规则:抽象和封装。从线程的观点来看,答应直接访问域只使对它进行非同步访问更轻易一些。 增加$property 要害字。带有此要害字的对象可被一个“bean 盒”应用程序访问,这个程序使用在 Class 类中定义的反射操作(introspection) API,否则与 private private 同效。 $property 属性可用在域和方式,这样现有的 JavaBean getter/setter 方式可以很轻易地被定义为属性。 不变性(immutability) 由于对不变对象的访问不需要同步,所以在多线程条件下,不变的概念(一个对象的值在创建后不可更改)是无价的。Java 编程言语中,对于不变性的实现不够严格,有两个原因:对于一个不变对象,在其被未完全创建之前,可以对它进行访问。这种访问对于某些域可以产生不准确的值。 对于恒定 (类的所有域都是 final) 的定义太松散。对于由 final 引用指定的对象,虽然引用本身不能改变,但是对象本身可以改变状态。 第一个问题可以解决,不答应线程在构造函数中开始执行 (或者在构造函数返回之前不能执行开始哀求)。 对于第二个问题,通过限定final 修饰符指向恒定对象,可以解决此问题。这就是说,对于一个对象,只有所有的域是 final,并且所有引用的对象的域也都是 final,此对象才真正是恒定的。为了不打破现有代码,这个定义可以使用编译器加强,即只有一个类被显式标为不变时,此类才是不变类。 有了$immutable 修饰符后,在域定义中的 final 修饰符是可选的。 最后,当使用内部类(inner class)后,在 Java 编译器中的一个错误使它无法可靠地创建不变对象。 既使空的 final 在每个构造函数中都有初始化,还是会出现这个错误信息。自从在 1.1 版本中引入内部类后,编译器中一直有这个错误。在此版本中(三年以后),这个错误依然存在。现在,该是改正这个错误的时候了。 对于类级域的实例级访问 除了访问权限外,还有一个问题,即类级(静态)方式和实例(非静态)方式都能直接访问类级(静态)域。这种访问是异常危险的,因为实例方式的同步不会获取类级的锁,所以一个synchronized static 方式和一个 synchronized 方式还是能同时访问类的域。改正此问题的一个明显的方式是,要求在实例方式中只有使用 static 访问方式才能访问非不变类的 static 域。当然,这种要求需要编译器和运行时间检查。 由于f() 和 g() 可以并行运行,所以它们能同时改变 x 的值(产生不定的结果)。请记住,这里有两个锁: static 方式要求属于 Class 对象的锁,而非静态方式要求属于此类实例的锁。 或则,编译器应获得读/写锁的使用: 另外一种方式是(这也是一种理想的 方式)-- 编译器应 自动 使用一个读/写锁来同步访问非不变 static 域,这样,程序员就不必担心这个问题。 后台线程的忽然结束 当所有的非后台线程终止后,后台线程都被忽然结束。当后台线程创建了一些全局资源(例如一个数据库连接或一个临时文件),而后台线程结束时这些资源没有被关闭或删除就会导致问题。 对于这个问题,我建议制定规则,使 Java 虚拟机在下列情况下不关闭应用程序:有任何非后台线程正在运行,或者: 有任何后台线程正在执行一个 synchronized 方式或 synchronized 代码块。 后台线程在它执行完synchronized 块或 synchronized 方式后可被立刻关闭。 重新引入stop() 、 suspend() 和 resume() 要害字 由于实用原因这也许不可行,但是我希望不要废除stop() (在 Thread 和 ThreadGroup 中)。但是,我会改变 stop() 的语义,使得调用它时不会破坏已有代码。但是,关于 stop() 的问题,请记住,当线程终止后, stop() 将释放所有锁,这样可能潜在地使正在此对象上工作的线程进入一种不稳定(局部修改)的状态。由于停止的线程已释放它在此对象上的所有锁,所以这些对象无法再被访问。 对于这个问题,可以重新定义stop() 的行为,使线程只有在不占有任何锁时才立刻终止。假如它占据着锁,我建议在此线程释放最后一个锁后才终止它。可以使用一个和抛出非常相似的机制来实现此行为。被停止线程应设置一个标志,并且当退出所有同步块时立刻测试此标志。假如设置了此标志,就抛出一个隐式的非常,但是此非常应不再能被捕获并且当线程结束时不会产生任何输出。注重,微软的 NT 操作系统不能很好地处理一个外部指示的忽然停止(abrupt)。(它不把 stop 消息通知动态连接库,所以可能导致系统级的资源漏洞。)这就是我建议使用类似非常的方式简朴地导致 run() 返回的原因。 与这种和非常类似的处理方式带来的实际问题是,你必需在每个synchronized 块后都插入代码来测试“stopped”标志。并且这种附加的代码会降低系统性能并增加代码长度。我想到的另外一个办法是使 stop() 实现一个“延迟的(lazy)”停止,在这种情况下,在下次调用 wait() 或 yield() 时才终止。我还想向 Thread 中加入一个 isStopped() 和 stopped() 方式(此时, Thread 将像 isInterrupted() 和 interrupted() 相同工作,但是会检测 “stop-requested”的状态)。这种方式不向第一种那样通用,但是可行并且不会产生过载。 应把suspend() 和 resume() 方式放回到 Java 编程语言中,它们是很有用的,我不想被当成是幼儿园的小孩。由于它们可能产生潜在的危险(当被挂起时,一个线程可以占据一个锁)而去掉它们是没有道理的。请让我自己来决定是否使用它们。假如接收的线程正占据着锁,Sun 公司应该把它们作为调用 suspend() 的一个运行时间非常处理(run-time exception);或者更好的方式是,延迟实际的挂起过程,直到线程释放所有的锁。 被阻断的 I/O 应准确工作 应该能打断任何被阻断的操作,而不是只让它们wait() 和 sleep() 。我在“ Taming Java Threads ”的第二章中的 socket 部分讨论了此问题。但是现在,对于一个被阻断的 socket 上的 I/O 操作,打断它的唯一办法是关闭这个 socket,而没有办法打断一个被阻断的文件 I/O 操作。例如,一旦开始一个读哀求并且进入阻断状态后,除非到它实际读出一些东西,否则线程一直出于阻断状态。既使关掉文件句柄也不能打断读操作。 还有,程序应支持 I/O 操作的超时。所有可能出现阻断操作的对象(例如 InputStream 对象)也都应支持这种方式。 这和 Socket 类的setSoTimeout(time) 方式是等价的。同样地,应该支持把超时作为参数传递到阻断的调用。 ThreadGroup类 ThreadGroup应该实现 Thread 中能够改变线程状态的所有方式。我特殊想让它实现 join() 方式,这样我就可等待组中的所有线程的终止。 总结 以上是我的建议。就像我在标题中所说的那样,假如我是国王...(哎)。我希望这些改变(或其它等同的方式)最终能被引入 Java 语言中。我确实认为 Java 语言是一种伟大的编程语言;但是我也认为 Java 的线程模型设计得还不够完善,这是一件很可惜的事情。但是,Java 编程语言正在演变,所以还有可提高的前景。 返回类别: 教程 上一教程: Eclipse迅速上手Hibernate--2. 利用Hbm映射文件开发 下一教程: JAVA程序员必读:基础篇(6) 您可以阅读与"Java线程模型缺陷研究"相关的教程: · java线程的缺陷 · 100行Java代码构建一个线程池 · Java 理论与实践:嗨,我的线程到哪里去了? · Java开发中的线程安全选择与Swing[Z] · 生产者消费者模型的Java简朴实现 |
![]() ![]() |
快精灵印艺坊 版权所有 |
首页![]() ![]() ![]() ![]() ![]() ![]() ![]() |