技术开发 频道

Java线程模型缺陷研究

    第一个问题可以解决,不允许线程在构造函数中开始执行 (或者在构造函数返回之前不能执行开始请求)。

    对于第二个问题,通过限定final 修饰符指向恒定对象,可以解决此问题。这就是说,对于一个对象,只有所有的域是 final,并且所有引用的对象的域也都是 final,此对象才真正是恒定的。为了不打破现有代码,这个定义可以使用编译器加强,即只有一个类被显式标为不变时,此类才是不变类。

    有了$immutable 修饰符后,在域定义中的 final 修饰符是可选的。

    最后,当使用内部类(inner class)后,在 Java 编译器中的一个错误使它无法可靠地创建不变对象。

    既使空的 final 在每个构造函数中都有初始化,还是会出现这个错误信息。自从在 1.1 版本中引入内部类后,编译器中一直有这个错误。在此版本中(三年以后),这个错误依然存在。现在,该是改正这个错误的时候了。

    对于类级域的实例级访问

  除了访问权限外,还有一个问题,即类级(静态)方法和实例(非静态)方法都能直接访问类级(静态)域。这种访问是非常危险的,因为实例方法的同步不会获取类级的锁,所以一个synchronized static 方法和一个 synchronized 方法还是能同时访问类的域。改正此问题的一个明显的方法是,要求在实例方法中只有使用 static 访问方法才能访问非不变类的 static 域。当然,这种要求需要编译器和运行时间检查。

    由于f() 和 g() 可以并行运行,所以它们能同时改变 x 的值(产生不定的结果)。请记住,这里有两个锁: static 方法要求属于 Class 对象的锁,而非静态方法要求属于此类实例的锁。 

    另外一种方法是(这也是一种理想的 方法)-- 编译器应 自动 使用一个读/写锁来同步访问非不变 static 域,这样,程序员就不必担心这个问题。

    后台线程的突然结束

    当所有的非后台线程终止后,后台线程都被突然结束。当后台线程创建了一些全局资源(例如一个数据库连接或一个临时文件),而后台线程结束时这些资源没有被关闭或删除就会导致问题。

    对于这个问题,我建议制定规则,使 Java 虚拟机在下列情况下不关闭应用程序:有任何非后台线程正在运行,或者: 有任何后台线程正在执行一个 synchronized 方法或 synchronized 代码块。

    后台线程在它执行完synchronized 块或 synchronized 方法后可被立即关闭。

    重新引入stop() 、 suspend() 和 resume() 关键字

    由于实用原因这也许不可行,但是我希望不要废除stop() (在 Thread 和 ThreadGroup 中)。但是,我会改变 stop() 的语义,使得调用它时不会破坏已有代码。但是,关于 stop() 的问题,请记住,当线程终止后, stop() 将释放所有锁,这样可能潜在地使正在此对象上工作的线程进入一种不稳定(局部修改)的状态。由于停止的线程已释放它在此对象上的所有锁,所以这些对象无法再被访问。

    对于这个问题,可以重新定义stop() 的行为,使线程只有在不占有任何锁时才立即终止。如果它占据着锁,我建议在此线程释放最后一个锁后才终止它。可以使用一个和抛出异常相似的机制来实现此行为。被停止线程应设置一个标志,并且当退出所有同步块时立即测试此标志。如果设置了此标志,就抛出一个隐式的异常,但是此异常应不再能被捕捉并且当线程结束时不会产生任何输出。注意,微软的 NT 操作系统不能很好地处理一个外部指示的突然停止(abrupt)。(它不把 stop 消息通知动态连接库,所以可能导致系统级的资源漏洞。)这就是我建议使用类似异常的方法简单地导致 run() 返回的原因。

    与这种和异常类似的处理方法带来的实际问题是,你必需在每个synchronized 块后都插入代码来测试“stopped”标志。并且这种附加的代码会降低系统性能并增加代码长度。我想到的另外一个办法是使 stop() 实现一个“延迟的(lazy)”停止,在这种情况下,在下次调用 wait() 或 yield() 时才终止。我还想向 Thread 中加入一个 isStopped() 和 stopped() 方法(此时, Thread 将像 isInterrupted() 和 interrupted() 一样工作,但是会检测 “stop-requested”的状态)。这种方法不向第一种那样通用,但是可行并且不会产生过载。

0
相关文章