wait()、notify()为什么在Ojbect类下?
在jdk1.5以前,我们使用synchronized,而因为synchronized中的这把锁可以是任意对象的,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。
关于wait()暂停的是持有锁的对象,所以想调用wait()必须为:对象.wait();
notify()唤醒的是等待锁的对象,调用:对象.notify();
可以通过下面的代码理解。
Object obj = newObject();
synchronized(obj){
try{
obj.wait();
}catch(Exception e){}
obj.notify();
}
所以说,因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。
在jdk1.5以后,将同步synchronized替换成了Lock,将同步锁对象换成了Condition对象,并且Condition对象可以有多个,这样可以解决一个问题:比如说我们在多个生产者和消费者模式中:
boolean flag = false;
public synchronized void set(String name){
while(flag){//用while而不用if的原因,这样每个线程在wait等待醒来后都必须再次判断flag
try{this.wait();}catch(Exception e){}
}
Sytem.out.printLn("生产者");
flag = true;
this.notifyAll();//这将唤醒所有线程(本方线程和对方线程),消耗资源
}
public synchronized void out(){
whie(!flag){
try{this.wait();}catch(Exception e){}
}
Sytem.out.printLn("消费者");
flag = false;
this.notifyAll();//这将唤醒所有线程(本方线程和对方线程),消耗资源
}
上面的做法很消耗资源,如果把notifyAll()改成notify()的话,就会造成可能所有线程都在等待。
Condition初探
所以在jdk1.5以后提供了Lock接口和Condition对象。Condition中的await(), signal().signalAll()代替Object中的wait(),notify(),notifyAll()
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();//生产者对象
private Condition condition_con = lock.newCondition();//消费者对象
public void set(String name) throws Exception{
lock.lock();//加锁
try{
while(flag){
contion_pro.await();
}
Sytem.out.printLn("生产者");
flag= true;
condition_con.singal();//指定唤醒消费方
}finally{
lock.unlock();//解锁
}
}
public void out() throws Exception{
lock.lock();
try{
while(!flag){
condition_con.await();
}
Sytem.out.printLn("消费者");
flag = false;
condition_pro.signal();//指定唤醒生产方
}finally{
lock.unlock();
}
}
这样做的好处,我们可以指定唤醒某一方,减少消耗。
从整体上来看Object的wait和notify/notify是与对象监视器配合完成线程间的等待/通知机制,而Condition与Lock配合完成等待通知机制,前者是java底层级别的,后者是语言级别的,具有更高的可控制性和扩展性。两者除了在使用方式上不同外,在功能特性上还是有很多的不同:
- Condition能够支持不响应中断,而通过使用Object方式不支持;
- Condition能够支持多个等待队列(new 多个Condition对象),而Object方式只能支持一个;
- Condition能够支持超时时间的设置,而Object不支持
参照Object的wait和notify/notifyAll方法,Condition也提供了同样的方法:
- void await() throws InterruptedException:当前线程进入等待状态,如果其他线程调用condition的signal或者signalAll方法并且当前线程获取Lock从await方法返回,如果在等待状态中被中断会抛出被中断异常;
- long awaitNanos(long nanosTimeout):当前线程进入等待状态直到被通知,中断或者超时;
- boolean await(long time, TimeUnit unit)throws InterruptedException:同第二种,支持自定义时间单位
- boolean awaitUntil(Date deadline) throws InterruptedException:当前线程进入等待状态直到被通知,中断或者到了某个时间
- void signal():唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回。
- void signalAll():与1的区别在于能够唤醒所有等待在condition上的线程
Condition实现原理分析
注意到ConditionObject中有两个成员变量:
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
这样我们就可以看出来ConditionObject通过持有等待队列的头尾指针来管理等待队列,所以condition的实现原理其实与AQS的同步队列非常相似。这里直接用图来表示。
注意:这里同步队列是AQS拥有的,等待队列是Condition拥有的,他们的节点都保存了线程相关的信息。
await()的实现:
为了介绍等待队列,首先来看下面一段代码:
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
thread.start();
}
}
这段代码没有任何实际意义,甚至很臭,只是想说明下我们刚才所想的。新建了10个线程,没有线程先获取锁,然后调用condition.await方法释放锁将当前线程加入到等待队列中,通过debug控制当走到第10个线程的时候查看firstWaiter即等待队列中的头结点,debug模式下情景图如下:
从上图我们可以很清楚的看到这样几点:1. 调用condition.await方法后线程依次尾插入到等待队列中,如图队列中的线程引用依次为Thread-0,Thread-1,Thread-2….Thread-8;2. 等待队列是一个单向队列。
如图所示:
同时还有一点需要注意的是:我们可以多次调用lock.newCondition()方法创建多个condition对象,也就是一个lock可以持有多个等待队列。示意图如下:
await()方法实现过程如下:
由上图所示:
调用condition.await方法的线程必须是已经获得了lock,也就是当前线程是同步队列中的头结点。调用该方法后会使得当前线程所封装的Node尾插入到等待队列中。
sinal()方法
调用condition的signal或者signalAll方法可以将等待队列中等待时间最长的节点移动到同步队列中,使得该节点能够有机会获得lock。按照等待队列是先进先出(FIFO)的,所以等待队列的头节点必然会是等待时间最长的节点,也就是每次调用condition的signal方法是将头节点移动到同步队列中。