被线程坑惨了!java中的wait、notify、notifyAll详解
在java中,线程间的通信可以使用wait、notify、notifyAll来进行控制。从名字就可以看出来这3个方法都是跟多线程相关的,但是可能让你感到吃惊的是:这3个方法并不是Thread类或者是Runnable接口的方法,而是Object类的3个本地方法。
下图是我总结的Java资料,想要资料的话请扫描下方二维码。

其实要理解这一点也并不难,调用一个Object的wait与notify/notifyAll的时候,必须保证调用代码对该Object是同步的,也就是说必须在作用等同于synchronized(obj){…}的内部才能够去调用obj的wait与notify/notifyAll三个方法,否则就会报错:
java.lang.IllegalMonitorStateException:current thread not owner
- 1
也就是说,在调用这3个方法的时候,当前线程必须获得这个对象的锁,那么这3个方法就是和对象锁相关的,所以是属于Object的方法而不是Thread,因为不是每个对象都是Thread。所以我们在理解wait、notify、notifyAll之前,先要了解以下对象锁。
多个线程都持有同一个对象的时候,如果都要进入synchronized(obj){…}的内部,就必须拿到这个对象的锁,synchronized的机制保证了同一时间最多只能有1个线程拿到了对象的锁,如下图:

下面我们来看一下这3个方法的作用:
- wait:线程自动释放其占有的对象锁,并等待notify
- notify:唤醒一个正在wait当前对象锁的线程,并让它拿到对象锁
- notifyAll:唤醒所有正在wait前对象锁的线程
notify和notifyAll的最主要的区别是:notify只是唤醒一个正在wait当前对象锁的线程,而notifyAll唤醒所有。值得注意的是:notify是本地方法,具体唤醒哪一个线程由虚拟机控制;notifyAll后并不是所有的线程都能马上往下执行,它们只是跳出了wait状态,接下来它们还会是竞争对象锁。
下面通过一个常用生产者、消费者的例子来说明。
消息实体类:
-
package com.podongfeng; -
/** -
* Title: Message.class<br> -
* Description: 消息实体<br> -
* Create DateTime: 2016年04月17日 下午1:27 <br> -
* -
* @author podongfeng -
*/ -
public class Message { -
}
生产者:
-
package com.podongfeng; -
import java.util.ArrayList; -
import java.util.List; -
/** -
* Title: Producer.class<br> -
* Description: 消息生产者<br> -
* Create DateTime: 2016年04月17日 下午1:28 <br> -
* -
* @author podongfeng -
*/ -
public class Producer extends Thread { -
List<Message> msgList = new ArrayList<>(); -
@Override public void run() { -
try { -
while (true) { -
Thread.sleep(3000); -
Message msg = new Message(); -
synchronized(msgList) { -
msgList.add(msg); -
msgList.notify(); //这里只能是notify而不能是notifyAll,否则remove(0)会报java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 -
} -
} -
} catch (Exception e) { -
e.printStackTrace(); -
} -
} -
public Message waitMsg() { -
synchronized(msgList) { -
if(msgList.size() == 0) { -
try { -
msgList.wait(); -
} catch(InterruptedException e) { -
e.printStackTrace(); -
} -
} -
return msgList.remove(0); -
} -
} -
}
消费者:
-
package com.podongfeng; -
/** -
* Title: Consumer.class<br> -
* Description: 消息消费者<br> -
* Create DateTime: 2016年04月17日 下午1:28 <br> -
* -
* @author podongfeng -
*/ -
public class Consumer extends Thread { -
private Producer producer; -
public Consumer(String name, Producer producer) { -
super(name); -
this.producer = producer; -
} -
@Override public void run() { -
while (true) { -
Message msg = producer.waitMsg(); -
System.out.println("Consumer " + getName() + " get a msg"); -
} -
} -
public static void main(String[] args) { -
Producer p = new Producer(); -
p.start(); -
new Consumer("Consumer1", p).start(); -
new Consumer("Consumer2", p).start(); -
new Consumer("Consumer3", p).start(); -
} -
}
消费者线程调用waitMsg去获取一个消息实体,如果msgList为空,则线程进入wait状态;生产这线程每隔3秒钟生产出体格msg实体并放入msgList列表,完成后,调用notify唤醒一个消费者线程去消费。
最后再次提醒注意:
wait、notify、notifyAll并不是Thread类或者是Runnable接口的方法,而是Object类的3个本地方法。
在调用这3个方法的时候,当前线程必须获得这个对象的锁
最后的惯例:点个小赞,好运不断,来个关注,青春常驻,打个小赏,工资疯涨。。。
本文详细介绍了Java中线程间通信的关键方法wait、notify和notifyAll,强调这三个方法属于Object类而非Thread类。在调用时,当前线程必须持有对象锁。wait会使线程释放锁并等待,notify会唤醒一个等待该对象锁的线程,而notifyAll则唤醒所有。通过生产者-消费者例子展示了它们的实际应用。理解这些方法对于多线程编程至关重要。


281

被折叠的 条评论
为什么被折叠?



