📄 如果我是国王:关于解决 java 编程语言线程问题的建议.htm
字号:
{ handler.handle_exception( problem );
}
}
}
);
}
}
</CODE>
</PRE></TD></TR></TBODY></TABLE>
<P>所有的写请求都用一个 <CODE>dispatch()</CODE> 过程调用被放在 active-object
的输入队列中排队。在后台处理这些异步信息时出现的任何异常 (exception) 都由
<CODE>Exception_handler</CODE> 对象处理,此 <CODE>Exception_handler</CODE>
对象被传送到 <CODE>File_io_task</CODE> 的构造函数中。您要写内容到文件时,代码如下:</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE><CODE> File_io_task io = new File_io_task
( "foo.txt"
new Exception_handler
<A id=.handle(Throwable) name=.handle(Throwable)> { public void handle( Throwable e )</A>
{ e.printStackTrace();
}
}
);
//...
io.write( some_bytes );</CODE>
</PRE></TD></TR></TBODY></TABLE>
<P>这种基于类的处理方法,其主要问题是太复杂了 -- 对于一个这样简单的操作,代码太杂了。向 Java 语言引入
<CODE>$task</CODE> 和 <CODE>$asynchronous</CODE>
关键字后,就可以按下面这样重写以前的代码:</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE><CODE> $task File_io $error{ $.printStackTrace(); }
{
OutputStream file;
File_io( String file_name ) throws IOException
{ file = new FileOutputStream( file_name );
}
asynchronous public write( byte[] bytes )
{ file.write( bytes );
}
}</CODE>
</PRE></TD></TR></TBODY></TABLE>
<P>注意,异步方法并没有指定返回值,因为其句柄将被立即返回,而不用等到请求的操作处理完成后。所以,此时没有合理的返回值。对于派生出的模型,
<CODE>$task</CODE> 关键字和 <CODE>class</CODE> 一样同效:<CODE>$task</CODE>
可以实现接口、继承类和继承的其它任务。标有 <CODE>asynchronous</CODE> 关键字的方法由
<CODE>$task</CODE> 在后台处理。其它的方法将同步运行,就像在类中一样。</P>
<P><CODE>$task</CODE> 关键字可以用一个可选的 <CODE>$error</CODE> 从句修饰
(如上所示),它表明对任何无法被异步方法本身捕捉的异常将有一个缺省的处理程序。我使用 <CODE>$</CODE>
来代表被抛出的异常对象。如果没有指定 <CODE>$error</CODE>
从句,就将打印出一个合理的出错信息(很可能是堆栈跟踪信息)。</P>
<P>注意,为确保线程安全,异步方法的参数必须是不变 (immutable)
的。运行时系统应通过相关语义来保证这种不变性(简单的复制通常是不够的)。</P>
<P>所有的 task 对象必须支持一些伪信息 (pseudo-message),例如:</P>
<TABLE cellSpacing=0 cellPadding=4 border=1>
<TBODY>
<TR>
<TD align=left width="25%"><CODE>some_task.close()</CODE></TD>
<TD align=left>在此调用后发送的任何异步信息都产生一个
<CODE>TaskClosedException</CODE>。但是,在 active
对象队列上等候的消息仍能被提供。</TD></TR>
<TR>
<TD align=left width="25%"><CODE>some_task.join()</CODE></TD>
<TD
align=left>调用程序被阻断,直到此任务关闭、而且所有未完成的请求都被处理完毕。</TD></TR></TBODY></TABLE><BR>除了常用的修饰符(<CODE>public</CODE>
等),<CODE>task</CODE> 关键字还应接受一个 <CODE>$pooled(n)</CODE> 修饰符,它导致
<CODE>task</CODE> 使用一个线程池,而不是使用单个线程来运行异步请求。<CODE>n</CODE>
指定了所需线程池的大小;必要时,此线程池可以增加,但是当不再需要线程时,它应该缩到原来的大小。伪域 (pseudo-field)
$pool_size 返回在 <CODE>$pooled(n)</CODE> 中指定的原始 <CODE>n</CODE> 参数值。
<P>在《<I>Taming Java Threads</I>》的第八章中,我给出了一个服务器端的 socket
处理程序,作为线程池的例子。它是关于使用线程池的任务的一个好例子。其基本思路是产生一个独立对象,它的任务是监控一个服务器端的
socket。每当一个客户机连接到服务器时,服务器端的对象会从池中抓取一个预先创建的睡眠线程,并把此线程设置为服务于客户端连接。socket
服务器会产出一个额外的客户服务线程,但是当连接关闭时,这些额外的线程将被删除。实现 socket 服务器的推荐语法如下:</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE><CODE><A id=.pooled(10) name=.pooled(10)> public $pooled(10) $task Client_handler</A>
{
PrintWriter log = new PrintWriter( System.out );
<A id=.handle(Socket) name=.handle(Socket)> public asynchronous void handle( Socket connection_to_the_client )</A>
{
log.println("writing");
// client-handling code goes here. Every call to
// handle() is executed on its own thread, but 10
// threads are pre-created for this purpose. Additional
// threads are created on an as-needed basis, but are
// discarded when handle() returns.
}
}
$task Socket_server
{
ServerSocket server;
Client_handler client_handlers = new Client_handler();
<A id=.Socket_server(int) name=.Socket_server(int)> public Socket_server( int port_number )</A>
{ server = new ServerSocket(port_number);
}
<A id=.listen(Client_handler) name=.listen(Client_handler)> public $asynchronous listen(Client_handler client)</A>
{
// This method is executed on its own thread.
while( true )
{ client_handlers.handle( server.accept() );
}
}
}
//...
Socket_server = new Socket_server( the_port_number );
server.listen()</CODE>
</PRE></TD></TR></TBODY></TABLE>
<P><CODE>Socket_server</CODE> 对象使用一个独立的后台线程处理异步的
<CODE>listen()</CODE> 请求,它封装 socket
的“接受”循环。当每个客户端连接时,<CODE>listen()</CODE> 请求一个
<CODE>Client_handler</CODE> 通过调用 <CODE>handle()</CODE> 来处理请求。每个
<CODE>handle()</CODE> 请求在它们自己的线程中执行(因为这是一个 <CODE>$pooled</CODE>
任务)。</P>
<P>注意,每个传送到 <CODE>$pooled $task</CODE>
的异步消息实际上都使用它们自己的线程来处理。典型情况下,由于一个 <CODE>$pooled $task</CODE>
用于实现一个自主操作;所以对于解决与访问状态变量有关的潜在的同步问题,最好的解决方法是在
<CODE>$asynchronous</CODE> <CODE></CODE>方法中使用 <CODE>this</CODE>
是指向的对象的一个独有副本。这就是说,当向一个 <CODE>$pooled $task</CODE> 发送一个异步请求时,将执行一个
<CODE>clone()</CODE> 操作,并且此方法的 <CODE>this</CODE>
指针会指向此克隆对象。线程之间的通信可通过对 <CODE>static</CODE> 区的同步访问实现。</P><A id=2
name=2></A>
<P><STRONG class=subhead>改进
<CODE>synchronized</CODE></STRONG><BR>虽然在多数情况下, <CODE>$task</CODE>
消除了同步操作的要求,但是不是所有的多线程系统都用任务来实现。所以,还需要改进现有的线程模块。
<CODE>synchronized</CODE> 关键字有下列缺点:</P>
<UL>
<LI>无法指定一个超时值。
<LI>无法中断一个正在等待请求锁的线程。
<LI>无法安全地请求多个锁 。(多个锁只能以依次序获得。) </LI></UL>
<P>解决这些问题的办法是:扩展 <CODE>synchronized</CODE>
的语法,使它支持多个参数和能接受一个超时说明(在下面的括弧中指定)。下面是我希望的语法:</P>
<TABLE cellSpacing=0 cellPadding=4 width=619 border=1>
<TBODY>
<TR>
<TD align=left width=205><CODE>synchronized(x && y
&& z)</CODE></TD>
<TD align=left width=392>获得 <CODE>x</CODE>、<CODE>y</CODE>
<U>和</U> <CODE>z</CODE> 对象的锁。</TD></TR>
<TR>
<TD align=left width=205><CODE>synchronized(x || y ||
z)</CODE></TD>
<TD align=left width=392><A id=2 name=2>获得
<CODE>x</CODE>、<CODE>y</CODE> <U>或</U> <CODE>z</CODE>
对象的锁。</A> </TD></TR>
<TR>
<TD align=left width=205><CODE>synchronized( (x && y )
|| z)</CODE></TD>
<TD align=left width=392>对于前面代码的一些扩展。</TD></TR>
<TR>
<TD align=left
width=205><CODE>synchronized(...)[1000]</CODE></TD>
<TD align=left width=392>设置 1 秒超时以获得一个锁。</TD></TR>
<TR>
<TD align=left width=205><CODE>synchronized[1000]
f(){...}</CODE></TD>
<TD align=left width=392>在进入 <A id=2
name=2><CODE>f()</CODE></A> 函数时获得 <CODE>this</CODE> 的锁,但可有 1
秒超时。</TD></TR></TBODY></TABLE>
<P><CODE>TimeoutException</CODE> 是 <CODE>RuntimeException</CODE>
派生类,它在等待超时后即被抛出。</P>
<P>超时是需要的,但还不足以使代码强壮。您还需要具备从外部中止请求锁等待的能力。所以,当向一个等待锁的线程传送一个
<CODE>interrupt()</CODE> 方法后,此方法应抛出一个
<CODE>SynchronizationException</CODE> 对象,并中断等待的线程。这个异常应是
<CODE>RuntimeException</CODE> 的一个派生类,这样不必特别处理它。</P>
<P>对 <CODE>synchronized</CODE>
语法这些推荐的更改方法的主要问题是,它们需要在二进制代码级上修改。而目前这些代码使用进入监控(enter-monitor)和退出监控(exit-monitor)指令来实现
<CODE>synchronized</CODE>。而这些指令没有参数,所以需要扩展二进制代码的定义以支持多个锁定请求。但是这种修改不会比在
Java 2 中修改 Java 虚拟机的更轻松,但它是向下兼容现存的 Java 代码。</P>
<P>另一个可解决的问题是最常见的死锁情况,在这种情况下,两个线程都在等待对方完成某个操作。设想下面的一个例子(假设的):</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>class Broken
{ Object lock1 = new Object();
Object lock2 = new Object();
void a()
{ synchronized( lock1 )
{ synchronized( lock2 )
{ // do something
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -