修正Java中wait方法超时语意模糊性的一种方案

本文分析Java中wait方法超时语意的模糊性,并提供通用解决方案。通过自定义条件类,如QueueFullCondition,实现多条件等待,避免嵌套monitor引起的死锁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

          if (timeSoFar >= msecTimeout)
            throw new TimeoutException ();
          else
            waitTime = timeout - timeSoFar;
        }
        else
          break; 
      }
    }
  }
  public final void announce() {
    object_.notifyAll ();
  }
}





回页首


使用方法介绍

本小节我们将对上一节给出的抽象基类WaitWithTiming的使用方法进行详细的介绍。我们当然可以直接使得ActiveQueue继承自WaitWithTiming,并实现相应的抽象hook方法condition,但是这样做有一个弊端,就是对于ActiveQueue我们只能够实现仅仅一个condition,如果我们要添加针对dequeue时队列为空的条件判断逻辑就无能为力了,因为WaitWithWaiting仅仅只有一个condition方法(其实,即使有多个也没有办法做到通用,因为不能对具体的应用的需求进行假设)。

我们推荐的使用方法是,根据具体应用的需求,整理出需要的判断条件,创建相应的类来表示这些判断条件,使这些用来表示具体判断条件的类继承自WaitWithTiming,这些类中具体的条件判断逻辑的实现可以使用相应的具体的应用实体。比如:对于本文开始所列举的应用,我们需要的判断条件为队列为满,所以我们可以定义一个QueueFullCondition类继承自WaitWithTiming,在QueueFullCondition中实现抽象的hook方法condition的逻辑,在该逻辑中在使用ActiveQueue的isFull方法。使用这种委托的方法,我们就可以比较有效的解决一个对象同时需要多个判断条件的问题(不同的判断条件只需定义不同的子类即可)。相应的UML结构图和关键代码实现如下:

UML结构图:



关键代码片断:

class QueueFullCondition extends WaitWithTiming
{
  public QueueFullCondition (ActiveQueue aq)
  { super (aq); }  // 为WaitWithTiming中的object_赋值
  public boolean condition () {
    ActiveQueue aq = (ActiveQueue) object_; //使用ActiveQueue来实现具体的判断逻辑
    return aq.isFull ();
  }
}
		class ActiveQueue {
			...
public synchronized void enqueue(ClientRequest cr, long timeout) 
					throws InterruptedException, TimeoutException
{
   	  //具有时限控制的等待	
     queueFullCondition_.timedWait (timeout);
// 把用户请求添加进队列
//唤醒等待在ActiveQueue上的线程
queueFullCondition_.announce ();
   }
	...
  private QueueFullCondition queueFullCondition_  =  new QueueFullCondition (this);
	}





回页首


要注意的问题

如果读者朋友仔细观察的话,就会觉察到在WaitWithTiming类中的timedWait方法的定义中没有添加synchronized关键字,这一点是非常关键的,因为是为了避免在编写并发的Java应用时一个常见的死锁问题:嵌套的monitor。下面对于这个问题进行简单的介绍,关于这一问题更为详细的论述请参见参考文献〔1〕。

什么是嵌套的monitor问题呢?嵌套的monitor是指:当一个线程获得了对象A的monitor锁,接着又获得了对象B的monitor锁,在还没有释放对象B的monitor锁时,调用了对象B的wait方法,此时,该线程释放对象B的monitor锁并等待在对象B的线程等待队列上,但是此时该线程还拥有对象A的monitor锁。如果该线程的唤起条件依赖于另一个线程首先要获得对象A的monitor锁的话,就会引起死锁,因为此时别的线程无法获得上述线程还没有释放的对象A的monitor锁,结果就出现了死锁情况。一般的解决方案是:在设计时线程不要获取对象B的monitor锁,而仅仅使用对象A的monitor锁。

针对我们前面列举的例子,ActiveQueu可以类比为对象A,queueFullContion_可以类比为对象B,如果我们在timedWait方法前面添加上synchronized关键字,就有可能会发生上述的死锁情况,因为当我们在调用ActiveQueu的enqueue方法中调用了queueFullContion_的timedWait方法后,如果队列为满,虽然我们释放了queueFullContion_的monitor锁,但是我们还持有ActiveQueue的monitor锁,并且我们的唤醒条件依赖于另外一个线程调用ActivcQueue的dequeue方法,但是因为此时我们没有释放ActiveQueue的monitor锁,所以另外的线程就无法调用ActiveQueu的dequeue方法,那么结果就是这两个线程就都只能够等待。





回页首


总结

本文对于Java中wait方法超时语意的模糊性进行了分析,并给出了一个比较通用的解决方案,本解决方案对于需要精确的超时语意的应用还是无法很好的适用的,因为方案中所给出的关于超时计算的算法是不精确的。还有一点就是有关嵌套monitor的问题,在编写多线程的Java程序时一定要特别注意,否则非常容易引起死锁。其实,本文所讲述的所有问题的根源都是由于Java对于wait方法超时语意实现的模糊性造成的,如果在后续的Java版本中对此进行了修正,那么本文给出的解决方案就是多余的了。



参考资料

[1] Doug Lea, Concurrent Java: Design Principles and Patterns, Addison-Wesley, 1996

[2] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object- Oriented Software, Reading, MA: Addison-Wesley, 1995.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值