技术开发 频道

Java线程模型缺陷研究

    解决这些问题的办法是:扩展synchronized 的语法,使它支持多个参数和能接受一个超时说明(在下面的括弧中指定)。下面是我希望的语法:

    synchronized(x && y && z) :获得 x、y 和 z 对象的锁。
    synchronized(x || y || z) :获得 x、y 或 z 对象的锁。
    synchronized( (x && y ) || z) :对于前面代码的一些扩展。
    synchronized(...)[1000] :设置 1 秒超时以获得一个锁。
    synchronized[1000] f(){...} :在进入 f() 函数时获得 this 的锁,但可有 1 秒超时。

    TimeoutException是 RuntimeException 派生类,它在等待超时后即被抛出。

    超时是需要的,但还不足以使代码强壮。您还需要具备从外部中止请求锁等待的能力。所以,当向一个等待锁的线程传送一个interrupt() 方法后,此方法应抛出一个 SynchronizationException 对象,并中断等待的线程。这个异常应是 RuntimeException 的一个派生类,这样不必特别处理它。

    对synchronized 语法这些推荐的更改方法的主要问题是,它们需要在二进制代码级上修改。而目前这些代码使用进入监控(enter-monitor)和退出监控(exit-monitor)指令来实现 synchronized 。而这些指令没有参数,所以需要扩展二进制代码的定义以支持多个锁定请求。但是这种修改不会比在 Java 2 中修改 Java 虚拟机的更轻松,但它是向下兼容现存的 Java 代码。

    另一个可解决的问题是最常见的死锁情况,在这种情况下,两个线程都在等待对方完成某个操作。

    设想一个线程调用a() ,但在获得  lock1 之后在获得 lock2 之前被剥夺运行权。 第二个线程进入运行,调用 b() ,获得了 lock2 ,但是由于第一个线程占用 lock1 ,所以它无法获得 lock1 ,所以它随后处于等待状态。此时第一个线程被唤醒,它试图获得 lock2 ,但是由于被第二个线程占据,所以无法获得。此时出现死锁。

    编译器(或虚拟机)会重新排列请求锁的顺序,使lock1 总是被首先获得,这就消除了死锁。

    但是,这种方法对多线程不一定总成功,所以得提供一些方法来自动打破死锁。一个简单的办法就是在等待第二个锁时常释放已获得的锁。

     如果等待锁的每个程序使用不同的超时值,就可打破死锁而其中一个线程就可运行。synchronized语句将永远等待,但是它时常会放弃已获得的锁以打破潜在的死锁可能。在理想情况下,每个重复等待的超时值比前一个相差一随机值。


    改进wait() 和 notify()

    wait()/ notify() 系统也有一些问题: 无法检测 wait() 是正常返回还是因超时返回。 无法使用传统条件变量来实现处于一个“信号”(signaled)状态。 太容易发生嵌套的监控(monitor)锁定。

    超时检测问题可以通过重新定义wait() 使它返回一个 boolean 变量 (而不是 void ) 来解决。一个 true 返回值指示一个正常返回,而 false 指示因超时返回。

    基于状态的条件变量的概念是很重要的。如果此变量被设置成false 状态,那么等待的线程将要被阻断,直到此变量进入 true 状态;任何等待 true 的条件变量的等待线程会被自动释放。 (在这种情况下, wait() 调用不会发生阻断。)。

    嵌套监控锁定问题非常麻烦,我并没有简单的解决办法。嵌套监控锁定是一种死锁形式,当某个锁的占有线程在挂起其自身之前不释放锁时,会发生这种嵌套监控封锁。

    此例中,在get() 和 put() 操作中涉及两个锁:一个在 Stack 对象上,另一个在 LinkedList 对象上。下面我们考虑当一个线程试图调用一个空栈的 pop() 操作时的情况。此线程获得这两个锁,然后调用 wait() 释放 Stack 对象上 的锁,但是没有释放在 list 上的锁。如果此时第二个线程试图向堆栈中压入一个对象,它会在 synchronized(list) 语句上永远挂起,而且永远不会被允许压入一个对象。由于第一个线程等待的是一个非空栈,这样就会发生死锁。这就是说,第一个线程永远无法从 wait() 返回,因为由于它占据着锁,而导致第二个线程永远无法运行到 notify() 语句。

0
相关文章