一段时间不用java,这些概念就全混淆了,有必要彻底澄清一下,总结在这里,当然以我的本事,不太可能写出水平较高的总结,这里主要是总结stackoverflow上面高人的言论。
先说sleep() 和 wait()
sleep() method causes the current thread to move from running state to block state for a specified time. If the current thread has the lock of any object then it keeps holding it, which means that other threads cannot execute any synchronized method in that class object.
sleep()这个方法是让当年线程从运行态转入阻塞态一段时间,如果当前线程拥有一些对象的锁,那么这个线程将继续拥有,也就意味着其他线程不能执行那些对象的synchronized方法。
注意,sleep()是Thread类的static方法,假如在一个Synchronized块中调用Thread的sleep()方法,虽然线程休眠,但是对象锁还会存在,
wait() method causes the current thread to go into block state either for a specified time or until notify, but in this case the thread releases the lock of the object (which means that other threads can execute any synchronized methods of the calling object.
wait()方法使当前的线程进入阻塞状态直到一段指定的时间结束或者被唤醒,调用wait()会释放对象的锁,也就意味着其他线程可以执行对象的synchronized方法。
注意,wait()方法是Object类的非静态方法,所以每个对象都可以调用。
wait()的使用
a). wait()必须放在synchronized块之中;
这个我前面有一篇blog刚好说这个的:
http://standalone.iteye.com/blog/788259
要执行wait()当前线程必须要先拿到锁。
b). wait()必须放在一个while loop中;
You need not only to loop it but check your condition in the loop. Java does not guarantee that your thread will be woken up only by a notify()/notifyAll() call or the right notify()/notifyAll() call at all. Because of this property the loop-less version might work on your development environment and fail on the production environment unexpectedly.
这个是说有可能睡眠的线程被唤醒不是靠的被notify()/notifyAll()或者被中断,或者时间到了,有一种情况叫
spurious wakeup, 你可以点进去看一下高人对此种虚假唤醒的解释。总之,因此要将wait放在一个while循环中,每次醒来自己去检查你的条件是否满足了。
Orcacle的文档也讲了:
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#wait(long)
还有另一种情况,看下面这个例子,如果不用while循环会出现什么错误。
(引自:
http://stackoverflow.com/questions/37026/java-notify-vs-notifyall-all-over-again)
public synchronized void put(Object o) {
while (buf.size()==MAX_SIZE) {
wait(); // called if the buffer is full (try/catch removed for brevity)
}
buf.add(o);
notify(); // called in case there are any getters or putters waiting
}
public synchronized Object get() {
// Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
while (buf.size()==0) {
wait(); // called if the buffer is empty (try/catch removed for brevity)
// X: this is where C1 tries to re-acquire the lock (see below)
}
Object o = buf.remove(0);
notify(); // called if there are any getters or putters waiting
return o;
}
假设这是一个ProducerConsumer类的两个方法,假如Consumer c1进入同步方法并且buffer是空的,c1会被阻塞在X处。这时候假如c2等着要进同步方法,它必须要拿到同步锁才能进来,所以会被阻塞在get()方法的开头处Y;假设Producer p1这时候生成了一个东西添加到buffer里面去了,然后notify(),此时因为wait的只有c1,所以c1会被唤醒,等着去拿同步锁,好继续往下执行。现在的情形是c1和c2都在抢同步锁!再假如c2幸运拿到了,那么c2会完整执行get()方法并且释放同步锁,这时候c1可以拿到,注意假如我们没有while循环,c1此时拿到锁之后直接往下执行,等着它的是一个空的buffer!会报IndexArrayOutOfBoundException!解决的办法就是需要while循环!
notify() vs. notifyAll()
notify() 是唤醒一个线程,notifyAll()是唤醒所有的线程,这个大家都知道。多数时候你应该用notifyAll,有时候如果需要用notifyAll()错用notify()会出错!仍然看上面那个例子。
简化情况,假如buffer的大小是1.
假如有如下的调用顺序:(P代表生产者线程,C代表消费者线程)
1:
P1放1个东西到buffer;
2:
P2准备放--发现已经满了--wait
3:
P3准备放--发现已经满了--wait
4:
C1准备从buffer里取一个,并成功
C2准备从buffer里取一个,阻塞在方法入口
C3准备从buffer里取一个,阻塞在方法入口
5:
C1释放同步锁,调用notify(),退出get方法
P2被唤醒
但是C2在P2之前抢到锁,所以P2会被继续阻塞
C2检查wait条件,发现buffer仍为空,继续阻塞
6:
现在P3,C2,C3都在等待
最终P2拿到锁,放一个东西到buffer, 调用notify,退出
7:
P2唤醒了P3(记住是随机的)
P3检查了一下发现buffer是满的,等待
现在没有任何线程再会调用notify,所有线程会被永远阻塞!死锁产生!
解决方案:将notify替换成notifyAll