昨天讲到线程的时候讲wait方法的时候,原以为直接写个wait()就ok了:
//实现Runnable接口
class World implements Runnable{
Thread thread;
public World(){
thread=new Thread(this);
}
@Override
public void run() {
//循环输出一句话
for(int i=0;i<10;){
System.out.println("!!world--->"+i);
if(i==7){
try {
//当i==7的时候线程线程等待'
System.out.println("值为8时线程world等待--->");
wait();
System.out.println("值为9时线程world被唤醒咯--->");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i++;
}
}
public static void main(String[] args) {
World world=new World();
//Thread thread=new Thread(world);
world.thread.start();
}
}
执行上面的方法后报错:
Exception in thread "Thread-0" !!world--->0java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at com.myjava.thread.World.run(World.java:33)
at java.lang.Thread.run(Thread.java:619)
!!world--->1
!!world--->2
!!world--->3
!!world--->4
!!world--->5
!!world--->6
!!world--->7
值为8时线程world等待--->
明确指出wait()方法报错,后来到网上查下资料,改成如下:
//实现Runnable接口
class World implements Runnable{
Thread thread;
public World(){
thread=new Thread(this);
}
@Override
public void run() {
//循环输出一句话
for(int i=0;i<10;){
System.out.println("!!world--->"+i);
if(i==7){
try {
//当i==7的时候线程线程等待'
synchronized (this) {
i++;
System.out.println("值为8时线程world等待--->");
wait(1000);//等待1秒后,线程重新进入队列执行
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
i++;
}
}
public static void main(String[] args) {
World world=new World();
//Thread thread=new Thread(world);
world.thread.start();
}
}
此时执行结果正确,没有报错,网上资料解释如下(具体哪个地方一下子忘记记录了):
初学者理解wait()的时候都认为是将当前线程阻塞,所以Thread.currentThread().wairt();视乎很有道理。但是不知道大家有没有发现,在JDK类库中wait()和notify()方法并不是Thread类的,而是Object()中的。我们仔细看看wait方法的JDK文档:
public final void wait() throws InterruptedException
在其他线程调用此对象的 notify()
方法或 notifyAll()
方法前,当前线程等待。 换句话说,此方法的行为就好像它仅执行wait(0) 调用一样。
当前线程必须拥有此 对象监视器 。该线程发布对此监视器的所有权并等待 ,直到其他线程通过调用 notify
方法,或notifyAll
方法通知在此对象的监视器上等待的线程醒来。 然后该线程将等到重新获得对监视器的所有权后才能继续执行。
对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
// Perform action appropriate to condition
}
此方法只应由作为此对象监视器的所有者的线程来调用。
抛出: IllegalMonitorStateException
- 如果当前线程不是此对象监视器的所有者。
InterruptedException
- 如果在当前线程等待通知之前或者正在等待通知时,任何线程中断了当前线程。在抛出此异常时,当前线程的中断状态 被清除。
看完JDK文档以后,很显然,只要把开始的程序中Thread.currentThread().wait();改成oa.wait() 。 notify();改成 ob.notify()就没有问题了。
也就是说,只能通过同步块obj来调用wait/notify方法 ,而不能通过想当然的线程调用这两个方法。至于为什么是这样,我有一种想法,大家可以一起讨论一下:
首先,我们都知道JVM会给每一个对象都分配唯一的一把锁。这把锁是在对象中的。
然后,当Thread-0线程获得了这把锁后,应该是在对象中的锁内记录下当前占有自己的线程号,并把自己设置为已被占用。那么当Thread-0需要放弃锁的时候,锁对象会把 Thread-0放入到锁的等待队列中 。而这一切和Thread-0是没有任何关系的。自然也轮不到Thread-0对象来调用某个方法来改变另一个对象中的锁(这一点也说不通,我自己的锁凭什么让你来改)。
因此,也就出现用改变公共数据区对象的锁的方法是通过共数据区对象本省来调用,和线程对象是没有关系的。
事实上,每一个同步锁lock下面都挂了几个线程队列,包括就绪(Ready)队列,等待(Waiting)队列等。当线程A因为得不到同步锁lock,从而进入的是lock.ReadyQueue(就绪队列),一旦同步锁不被占用,JVM将自动运行就绪队列中的线程而不需要任何notify()的操作。但是当线程A被wait()了,那么将进入lock.WaitingQuene(等待队列),同时如果占据的同步锁也会放弃。而此时如果同步锁不唤醒等待队列中的进程(lock.notify()),这些进程将永远不会得到运行的机会。
就绪队列和等待队列有很大的不同,这一点要牢记。