详解java中wait/notifiy/notifyAll
1. 基础使用方法
- 3个方法都必须在同步块中限定的作用域中调用,否则会报IllegalMonitorStateException异常
- wait(),表示持有对象锁的线程准备释放对象锁权限,释放cpu并堵塞线程。
- notify/notifyAll 表示持有对象锁的线程准备释放对象锁权限,然后jvm唤醒某个竞争该锁的线程。当notify 退出代码块后才真正释放锁。
- wait() 必须唤醒才会去竞争锁。
2. 状态依赖性的管理
2.1 什么是状态依赖性
2.1.1 定义
基于状态的前提条件。如不能从一个空的队列中删除元素,不能在满的队列中添加元素,不能从FutureTask 中获取尚未结束的计算结果,在这些可以执行之前,必须达到一定的前提条件,“非空”,“非满”,“已完成”
2.1.2 单线程和多线程的状态依赖性
在 单线程 任务中,如果某个方法依赖于某个前提条件,但是这个前提条件并没有满足(如:消费者从资源队列中获取一个资源,前提条件是队列不为空),那么这个条件永远无法成立。(无论如何等待,重试,都无法成立)因此在编写顺序程序的类,如果无法满足前提条件就返回失败。
在 多线程 任务中,基于某个前提条件的方法,前提条件会被其他线程所改变,可能目前资源队列中没有资源,10ms后又有资源了。这个前提条件目前可能是不满足,但是堵塞一下,等待一段时间,就有可能可以满足。并发对象上依赖状态方法,虽然有时候前提条件不满足的情况不会失败,但是有更好的选择,即:等待前提条件为真。
2.2 内置条件队列
条件队列:它使得一组线程(称之为等待线程集合),能够通过某种方式来等待特定的条件为真。传统队列是一个个数据,与之不同的是,条件队列中的元素是一个个正在等待相关条件的线程。
在java中,每个对象可以作为一个锁,同样可以作为一个条件队列,Object 中的wait,notify,notifyAll 构成内部队列的api。
wait() , 在状态前提条件不满足的情况下调用使得当前线程堵塞进入等待队列。
notify()/notifyAll(),在改变状态后,可能会满足某个条件,唤醒等待线程进行处理。
2.3 前提条件失败传递给调用者
// 有界队列存取方法,不满足条件抛出异常,返回给调用者
public synchronized void put(V v) throws BufferFullException{
if(isFull())
throw new BufferFullException();
doPut(v);
}
public synchronized V take() throws BufferEmptyException{
if(isEmpty())
throw new BufferEmptyException();
doTake(v);
}
在这段代码中,put/take方法,如果没有满足前提条件将会抛出异常返回给调用者,调用者进行决策进行重试或者其它处理。
这样存在一个问题,比如"缓存已满",并不是有界缓存的一个异常条件,就像红灯并不代表交通信号灯出现了异常,而且调用者处理代码将会非常的丑陋,如果调用者重试,那么每次调用take() , put() 方法就必须要这样写:
// 调用者代码
while(true){
try{
V item = buffer.take();
break;
}