关于解决 Java 编程语言线程问题的建议(3)

发表于:2007-07-14来源:作者:点击数: 标签:
修改 Thread 类 同时支持抢占式和协作式线程的能力在某些 服务器 应用程序中是基本要求,尤其是在想使系统达到最高 性能 的情况下。我认为 Java 编程语言在简化线程模型上走得太远了,并且 Java 编程语言应支持 Posix/Solaris 的“绿色(green)线程”和“轻便
修改 Thread 类

同时支持抢占式和协作式线程的能力在某些服务器应用程序中是基本要求,尤其是在想使系统达到最高性能的情况下。我认为
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
// aclearcase/" target="_blank" >ccess 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
正在致力于这项工作(请参阅参考资料)。

原文转自:http://www.ltesting.net