📄 如果我是国王:关于解决 java 编程语言线程问题的建议.htm
字号:
<TBODY>
<TR>
<TD><PRE> 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)...
}
</PRE></TD></TR></TBODY></TABLE>
<P>对于一个对象,应该只有在 <CODE>$writing</CODE> <CODE></CODE>块中没有线程时,才支持多个线程进入
<CODE>$reading</CODE> 块。在进行读操作时,一个试图进入 <CODE>$writing</CODE>
块的线程会被阻断,直到读线程退出 <CODE>$reading</CODE> 块。 当有其它线程处于
<CODE>$writing</CODE> 块时,试图进入 <CODE>$reading</CODE> 或
<CODE>$writing</CODE> 块的线程会被阻断,直到此写线程退出 <CODE>$writing</CODE>
<CODE></CODE>块。</P>
<P>如果读和写线程都在等待,缺省情况下,读线程会首先进行。但是,可以使用 <CODE>$writer_priority</CODE>
属性修改类的定义来改变这种缺省方式。如:</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>$write_priority class IO
{
$writing write( byte[] data )
{ //...
}
$reading byte[] read( )
{ //...
}
}
</PRE></TD></TR></TBODY></TABLE><A id=7 name=7></A>
<P><STRONG class=subhead>访问部分创建的对象应是非法的</STRONG><BR>当前情况下,JLS
允许访问部分创建的对象。例如,在一个构造函数中创建的线程可以访问正被创建的对象,既使此对象没有完全被创建。下面代码的结果无法确定:</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE> class Broken
{ private long x;
Broken()
{ new Thread()
{ public void run()
{ x = -1;
}
}.start();
x = 0;
}
}
</PRE></TD></TR></TBODY></TABLE>
<P>设置 <CODE>x</CODE> 为 -1 的线程可以和设置 <CODE>x</CODE> 为 0 的线程同时进行。所以,此时
<CODE>x</CODE> 的值无法预测。</P>
<P>对此问题的一个解决方法是,在构造函数没有返回之前,对于在此构造函数中创建的线程,既使它的优先级比调用
<CODE>new</CODE> 的线程高,也要禁止运行它的 <CODE>run()</CODE> 方法。</P>
<P>这就是说,在构造函数返回之前, <CODE>start()</CODE> 请求必须被推迟。</P>
<P>另外,Java 编程语言应可允许构造函数的同步。换句话说,下面的代码(在当前情况下是非法的)会象预期的那样工作:</P>
<P></P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE> class Illegal
{ private long x;
<B>synchronized</B> Broken()
{ new Thread()
{ public void run()
{ <B>synchronized( Illegal.this )
{</B>
x = -1;
<B>}</B>
}
}.start();
x = 0;
}
}
</PRE></TD></TR></TBODY></TABLE>
<P>我认为第一种方法比第二种更简洁,但实现起来更为困难。</P><A id=7a name=7a></A>
<P><STRONG class=subhead><CODE>volatile</CODE>
关键字应象预期的那样工作</STRONG><BR>JLS 要求保留对于 volatile 操作的请求。大多数 Java
虚拟机都简单地忽略了这部分内容,这是不应该的。在多处理器的情况下,许多主机都出现了这种问题,但是它本应由 JLS
加以解决的。如果您对这方面感兴趣,马里兰大学的 Bill Pugh 正在致力于这项工作(请参阅<A
href="http://www-900.ibm.com/developerWorks/cn/java/j-king/index.shtml#resources">参考资料</A>)。</P><A
id=8 name=8></A>
<P><STRONG
class=subhead>访问的问题</STRONG><BR>如果缺少良好的访问控制,会使线程编程非常困难。大多数情况下,如果能保证线程只从同步子系统中调用,不必考虑线程安全(threadsafe)问题。我建议对
Java 编程语言的访问权限概念做如下限制;</P>
<OL>
<LI>应精确使用 <CODE>package</CODE>
关键字来限制包访问权。我认为当缺省行为的存在是任何一种计算机语言的一个瑕疵,我对现在存在这种缺省权限感到很迷惑(而且这种缺省是“包(package)”级别的而不是“私有(private)”)。在其它方面,Java
编程语言都不提供等同的缺省关键字。虽然使用显式的 <CODE>package</CODE>
的限定词会破坏现有代码,但是它将使代码的可读性更强,并能消除整个类的潜在错误
(例如,如果访问权是由于错误被忽略,而不是被故意忽略)。
<LI>重新引入 <CODE>private protected</CODE>,它的功能应和现在的
<CODE>protected</CODE> 一样,但是不应允许包级别的访问。
<LI>允许 <CODE>private private</CODE>
语法指定“实现的访问”对于所有外部对象是私有的,甚至是当前对象是的同一个类的。对于“.”左边的唯一引用(隐式或显式)应是
<CODE>this</CODE>。
<LI>扩展 <CODE>public</CODE> 的语法,以授权它可制定特定类的访问。例如,下面的代码应允许
<CODE>Fred</CODE> 类的对象可调用
<CODE>some_method()</CODE>,但是对其它类的对象,这个方法应是私有的。
<LI style="LIST-STYLE-TYPE: none">
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE> public(Fred) void some_method()
{
}
</PRE></TD></TR></TBODY></TABLE>
<P>这种建议不同于 C++ 的 "friend" 机制。 在 "friend"
机制中,它授权一个类访问另一个类的<I>所有</I>私有部分。在这里,我建议对有限的方法集合进行严格控制的访问。用这种方法,一个类可以为另一个类定义一个接口,而这个接口对系统的其余类是不可见的。一个明显的变化是:</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE> public(Fred, Wilma) void some_method()
{
}
</PRE></TD></TR></TBODY></TABLE>
<LI>
<P>除非域引用的是真正不变(immutable)的对象或 <CODE>static final</CODE>
基本类型,否则所有域的定义应是 <CODE>private</CODE>。对于一个类中域的直接访问违反了 OO
设计的两个基本规则:抽象和封装。从线程的观点来看,允许直接访问域只使对它进行非同步访问更容易一些。</P>
<LI>
<P>增加 <CODE>$property</CODE> 关键字。带有此关键字的对象可被一个“bean
盒”应用程序访问,这个程序使用在 <CODE>Class</CODE> 类中定义的反射操作(introspection)
API,否则与 private private 同效。 <CODE>$property</CODE> 属性可用在域和方法,这样现有的
JavaBean getter/setter 方法可以很容易地被定义为属性。</P></LI></OL><A id=9
name=9></A>
<P><B>不变性(immutability)</B><BR>由于对不变对象的访问不需要同步,所以在多线程条件下,不变的概念(一个对象的值在创建后不可更改)是无价的。Java
编程言语中,对于不变性的实现不够严格,有两个原因:</P>
<UL>
<LI>对于一个不变对象,在其被未完全创建之前,可以对它进行访问。这种访问对于某些域可以产生不正确的值。
<LI>对于恒定 (类的所有域都是 <CODE>final)</CODE> 的定义太松散。对于由
<CODE>final</CODE> <CODE></CODE>引用指定的对象,虽然引用本身不能改变,但是对象本身可以改变状态。
</LI></UL>
<P>第一个问题可以解决,不允许线程在构造函数中开始执行 (或者在构造函数返回之前不能执行开始请求)。</P>
<P>对于第二个问题,通过限定 <CODE>final</CODE>
修饰符指向恒定对象,可以解决此问题。这就是说,对于一个对象,只有所有的域是 final,并且所有引用的对象的域也都是
final,此对象才真正是恒定的。为了不打破现有代码,这个定义可以使用编译器加强,即只有一个类被显式标为不变时,此类才是不变类。方法如下:</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE> <B>$immutable</B> public class Fred
{
// all fields in this class must be final, and if the
// field is a reference, all fields in the referenced
// class must be final as well (recursively).
static int x constant = 0; // use of `final` is optional when $immutable
// is present.
}
</PRE></TD></TR></TBODY></TABLE>
<P>有了 <CODE>$immutable</CODE> 修饰符后,在域定义中的 <CODE>final</CODE>
修饰符是可选的。</P>
<P>最后,当使用内部类(inner class)后,在 Java
编译器中的一个错误使它无法可靠地创建不变对象。当一个类有重要的内部类时(我的代码常有),编译器经常不正确地显示下列错误信息:</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>"Blank final variable '<I>name</I>' may not have been initialized.
It must be assigned a value in an initializer, or in every constructor."
</PRE></TD></TR></TBODY></TABLE>
<P>既使空的 final 在每个构造函数中都有初始化,还是会出现这个错误信息。自从在 1.1
版本中引入内部类后,编译器中一直有这个错误。在此版本中(三年以后),这个错误依然存在。现在,该是改正这个错误的时候了。</P><A
id=10 name=10></A>
<P><B>对于类级域的实例级访问</B><BR>除了访问权限外,还有一个问题
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -