wait() notify() notifyAll()
- wait和notify方法必须写在synchronized方法内,即在调用wait和notify方法前,需先获得对象锁;
- 调用wait方法则释放锁;wait方法返回后,需获得对象锁才可继续执行下面语句;
- 多个线程wait时,若另外的线程调用notify方法后,由JVM决定唤醒其中一个线程;
- 多个线程wait时,若另外的线程调用notifyAll方法,则唤醒所有wait线程,但是只有其中一个线程可以获得对象锁,执行wait下面的语句,其余的等待释放对象锁后才可执行;
- 调用notify/notifyAll时,线程并没有释放对象锁,只是其余线程被唤醒,但仍无法执行,只有等调用完notify/notifyAll并退出synchronized块,释放对象锁后,其余线程中的一个才可获得锁执行。
[wait(),notify()/notityAll()方法只能在同步方法中调用]
另:sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁。也就是说,如果有Synchronized块,其他线程仍然不能访问共享数据。
1、这两个方法来自不同的类分别是,
sleep()来自Thread类,
wait()来自Object类。
sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
2、最主要是
sleep方法没有释放锁,
而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
3、使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
4、sleep必须捕获异常,
而wait,notify和notifyAll不需要捕获异常
[synchornized方法]
如果一个方法声明为synchornized的,则等同于在这个方法上调用synchornized(this).
如果一个静态方法被声明为synchornized,则等同于在这个方法上调用synchornized(类.class).
现在进入wait方法和notify/notifyAll方法.这两个(或叫三个)方法都是Object对象的方法,而不是线程对象的方法.如同锁一样,它们是在线程中调用某一对象上执行的.
class Test{
public synchornized void test(){
//获取条件,int x 要求大于100;
if(x < 100) // 最好使用while(x<100)
wait(); //即 this.wait()
}
}
这里为了说明方法没有加在try{}catch(){}中,如果没有明确在哪个对象上调用wait()方法,则为this.wait();
假如:
Test t = new Test();
现在有两个线程都执行到t.test();方法.其中线程A获取了t的对象锁,进入test()方法内.这时x小于100,所以线程A进入等待.
当一个线程调用了wait方法后,这个线程就进入了这个对象的休息室(waitset),这是一个虚拟的对象,但JVM中一定存在这样的一个数据结构用来记录当前对象中有哪些程线程在等待.当一个线程进入等待时,它就会释放锁,让其它线程来获取这个锁.
线程A运行到this.wait(), 所以线程B有机会获得了线程A释放的锁,进入test()方法,如果这时x还是小于100,线程B也进入了t的休息室.这两个线程只能等待其它线程调用notity[All]来唤醒.
但是如果调用的是有参数的wait(time)方法,则线程A,B都会在休息室中等待这个时间后自动唤醒.
[为什么真正的应用都是用while(条件)而不用if(条件)]
在实际的编程中我们看到大量的例子都是用
while(x < 100)
wait();
go();而不是用if,为什么呢?
在多个线程同时执行时,if(x <100)是不安全的.因为如果线程A和线程B都在t的休息室中等待,
这时另一个线程使x==100了,并调用notifyAll方法,线程A继续执行下面的go().而它执行完成后,x有可能又小于100,比如下面的程序中调用了--x,这时切换到线程B,线程B没有继续判断,直接执行go();就产生一个
错误的条件,只有while才能保证线程B又继续检查一次.
[notify/notifyAll方法]
这两个方法都是把某个对象在休息区内的线程唤醒, notify只能唤醒一个,但究竟是哪一个不能确定,而notifyAll则唤醒这个对象上的休息室中所有的线程.
一般为了安全性,我们在绝对多数时候应该使用notifiAll(), 除非你明确知道只唤醒其中的一个线程.
那么是否是只要调用一个对象的wait()方法,当前线程就进入了这个对象的休息室呢?
事实中,要调用一个对象的wait()方法,只有当前线程获取了这个对象的锁 , 换句话说一定要在这个对象的同步方法 或 以这个对象为参数的同步块中.
class MyThread extends Thread{
Test t = new Test();
public void run(){
t.test();
System.out.println("Thread say:Hello,World!");
}
}
public class Test {
int x = 0;
public void test(){
if(x==0)
try{
wait(); // 即this.wait() wait()要放到同步代码块里面
}
catch(Exception e){}
}public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
这个线程就不会进入t的wait方法而直接打印出Thread say:Hello,World!.
而如果改成:
public class Test {int x = 0;
public synchornized void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
我们就可以看到线程一直等待, 注意这个线程进入等待后没有其它线程唤醒, 除非强行退出JVM环境,否则它一直等待.
所以请记住:
1,线程要想调用一个对象的wait()方法就要先获得该对象的监视锁,而一旦调用wait()后又立即释放该锁
2,如果要把notify/notifyAll和wait方法放在一起用的话,必须先调用notify/notifyAll后调用wait,因为如果调用完wait,该线程就已
经不是current thread了
3,wait(),notify(),notifyAll()方法必须出现在同步方法或同步代码块中