技术开发 频道

Java线程模型缺陷研究

    那么我的第一个建议是,向 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 关键字有下列缺点: 无法指定一个超时值。 无法中断一个正在等待请求锁的线程。 无法安全地请求多个锁 。(多个锁只能以依次序获得。)
0
相关文章