关于解决 Java 编程语言线程问题的建议(3)
修改 Thread 类
同时支持抢占式和协作式线程的能力在某些/server/ 服务器应用程序中是基本要求,尤其是在想使系统达到最高性能的情况下。我认为
Java 编程语言在简化线程模型上走得太远了,并且 Java 编程语言应支持
Posix/Solaris 的“绿色(green)线程”和“轻便(lightweight)进程”概念(在“(Taming Java Threads”第一章中讨论)。
这就是说,有些 Java 虚拟机的实现(例如在 NT 上的 Java 虚拟机)应在其内部仿真协作式进程,其它
Java 虚拟机应仿真抢占式线程。而且向 Java 虚拟机加入这些扩展是很容易的。
一个 Java 的 Thread 应始终是抢占式的。这就是说,一个 Java 编程语言的线程应像 Solaris
的轻便进程一样工作。 Runnable 接口可以用于定义一个 Solaris 式的“绿色线程”,此线程必需能把控制权转给运行
在相同轻便进程中的其它绿色线程。
例如,目前的语法:
class My_thread implements Runnable
{ public void run(){ /*...*/ }
}
new Thread( new My_thread );
能有效地为 Runnable 对象产生一个绿色线程,并把它绑定到由
Thread 对象代表的轻便进程中。这种实现对于现有代码是透明的,因为它的有效性和现有的完全一样。
把 Runnable 对象想成为绿色线程,使用这种方法,只需向
Thread 的构造函数传递几个 Runnable对象,就可以扩展
Java 编程语言的现有语法,以支持在一个单一轻便线程有多个绿色线程。(绿色线程之间可以相互协作,但是它们可被运行在其它轻便进程
(Thread 对象) 上的绿色进程(Runnable 对象) 抢占。)。例如,下面的代码会为每个
runnable 对象创建一个绿色线程,这些绿色线程会共享由 Thread 对象代表的轻便进程。
new Thread( new My_runnable_object(), new My_other_runnable_object() );
现有的覆盖(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”(和其它地方)中有详细讨论,概括地说:一个读写锁支持多个线程同时访问一个对象,但是在同一时刻只有一个线程可以修改此对象,并且在访问进行时不能修改。读写锁的语法可以借用 synchronized 关键字:
static Object global_resource;
//...
public void a()
{
$reading( global_resource )
{ // While in this block, other threads requesting read
// access to global_resource will get it, but threads
// requesting write access will block.
}
}
public void b()
{
$writing( global_resource )
{ // Blocks until all ongoing read or write operations on
// global_resource are complete. No read or write
// operation or global_resource can be initiated while
// within this block.
}
}
public $reading void c()
{ // just like $reading(this)...
}
public $writing void d()
{ // just like $writing(this)...
}
对于一个对象,应该只有在 $writing 块中没有线程时,才支持多个线程进入
$reading 块。在进行读操作时,一个试图进入 $writing
块的线程会被阻断,直到读线程退出 $reading
块。 当有其它线程处于 $writing 块时,试图进入 $reading
或 $writing 块的线程会被阻断,直到此写线程退出 $writing
块。
如果读和写线程都在等待,缺省情况下,读线程会首先进行。但是,可以使用
$writer_priority 属性修改类的定义来改变这种缺省方式。如:
$write_priority class IO
{
$writing write( byte[] data )
{ //...
}
$reading byte[] read( )
{ //...
}
}
访问部分创建的对象应是非法的
当前情况下,JLS 允许访问部分创建的对象。例如,在一个构造函数中创建的线程可以访问正被创建的对象,既使此对象没有完全被创建。下面代码的结果无法确定:
class Broken
{ private long x;
Broken()
{ new Thread()
{ public void run()
{ x = -1;
}
}.start();
x = 0;
}
}
设置 x 为 -1 的线程可以和设置 x 为 0 的线程同时进行。所以,此时 x 的值无法预测。
对此问题的一个解决方法是,在构造函数没有返回之前,对于在此构造函数中创建的线程,既使它的优先级比调用
new 的线程高,也要禁止运行它的 run() 方法。
这就是说,在构造函数返回之前, start()
请求必须被推迟。
另外,Java 编程语言应可允许构造函数的同步。换句话说,下面的代码(在当前情况下是非法的)会象预期的那样工作:
class Illegal
{ private long x;
synchronized Broken()
{ new Thread()
{ public void run()
{ synchronized( Illegal.this )
{
x = -1;
}
}
}.start();
x = 0;
}
}
我认为第一种方法比第二种更简洁,但实现起来更为困难。
volatile
关键字应象预期的那样工作
JLS 要求保留对于 volatile 操作的请求。大多数 Java 虚拟机都简单地忽略了这部分内容,这是不应该的。在多处理器的情况下,许多主机都出现了这种问题,但是它本应由
JLS 加以解决的。如果您对这方面感兴趣,马里兰大学的 Bill Pugh
正在致力于这项工作(请参阅参考资料)。
|
|
|