Answers:
的wait()
和notify()
方法被设计为提供一种机制,以允许一个线程块,直到一个特定的条件被满足。为此,我假设您要编写一个阻塞队列实现,其中具有一些固定大小的元素后备存储。
您要做的第一件事是确定您希望方法等待的条件。在这种情况下,您将希望该put()
方法阻塞直到存储空间可用,并且您将希望该take()
方法阻塞直到返回一些元素。
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void put(T element) throws InterruptedException {
while(queue.size() == capacity) {
wait();
}
queue.add(element);
notify(); // notifyAll() for multiple producer/consumer threads
}
public synchronized T take() throws InterruptedException {
while(queue.isEmpty()) {
wait();
}
T item = queue.remove();
notify(); // notifyAll() for multiple producer/consumer threads
return item;
}
}
关于必须使用等待和通知机制的方式,需要注意一些事项。
首先,您需要确保对代码的任何调用wait()
或notify()
在代码的同步区域内(并且wait()
和notify()
调用在同一对象上同步)。造成这种情况的原因(除了标准线程安全问题之外)是由于某种被称为信号丢失的原因造成的。
这样的一个示例是,put()
当队列碰巧已满时,线程可以调用,然后检查条件,发现队列已满,但是在可以阻塞另一个线程之前,该线程已调度。然后,第二个线程take()
是队列中的一个元素,并通知等待线程该队列不再满。因为第一个线程已经检查了条件,所以wait()
即使重新安排了进度,它也只会在重新安排之后调用。
通过在共享库上进行同步,可以确保不会发生此问题,因为在第take()
一个线程实际阻塞之前,第二个线程的调用将无法进行。
其次,由于称为虚假唤醒的问题,您需要将要检查的条件放入while循环中,而不是if语句中。这是一个等待线程有时可以在不notify()
调用的情况下重新激活的地方。将此检查放在while循环中将确保如果发生虚假唤醒,将重新检查条件,并且线程将wait()
再次调用。
就像其他答案中提到的那样,Java 1.5引入了一个新的并发库(在java.util.concurrent
包中),该库旨在在等待/通知机制上提供更高级别的抽象。使用这些新功能,您可以像这样重写原始示例:
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public void put(T element) throws InterruptedException {
lock.lock();
try {
while(queue.size() == capacity) {
notFull.await();
}
queue.add(element);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while(queue.isEmpty()) {
notEmpty.await();
}
T item = queue.remove();
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}
当然,如果您实际上需要阻塞队列,则应该使用BlockingQueue接口的实现 。
另外,对于此类事情,我强烈建议Java Concurrency in Practice,因为它涵盖了您可能想知道的与并发相关的问题和解决方案的所有内容。
不是队列示例,但是非常简单:)
class MyHouse {
private boolean pizzaArrived = false;
public void eatPizza(){
synchronized(this){
while(!pizzaArrived){
wait();
}
}
System.out.println("yumyum..");
}
public void pizzaGuy(){
synchronized(this){
this.pizzaArrived = true;
notifyAll();
}
}
}
重要事项:
1)从不做
if(!pizzaArrived){
wait();
}
始终使用while(condition),因为
while(!pizzaExists){ wait(); }
。2)在调用等待/无效之前,您必须保持锁(同步)。线程也必须在唤醒之前获取锁。
3)尝试避免在同步块中获取任何锁,并努力不调用外来方法(您不确定它们在做什么的方法)。如果需要,请确保采取措施避免死锁。
4)注意notify()。坚持使用notifyAll(),直到您知道自己在做什么。
5)最后但并非最不重要的一点,请阅读实践Java并发!
pizzaArrived
标志?如果在没有调用的情况下更改了标志notify
,则不会产生任何效果。也仅用于wait
和notify
调用示例即可。
synchronized
关键字保护时,声明该变量是多余的volatile
,建议您避免使用该变量,以避免混淆@mrida
即使你要求wait()
和notify()
具体而言,我觉得这句话仍然是足够重要:
Josh Bloch,Effective Java 2nd Edition,第69项:并发实用程序优先于wait
和notify
(强调他的):
考虑到使用
wait
和notify
正确的困难,与相比,您应该使用更高级别的并发实用程序,而不是像使用“并发汇编语言”进行编程那样直接使用wait
和。有很少,如果有的话,有理由使用,并在新的代码。notify
java.util.concurrent
wait
notify
notify()
的,并wait()
再次
例
public class myThread extends Thread{
@override
public void run(){
while(true){
threadCondWait();// Circle waiting...
//bla bla bla bla
}
}
public synchronized void threadCondWait(){
while(myCondition){
wait();//Comminucate with notify()
}
}
}
public class myAnotherThread extends Thread{
@override
public void run(){
//Bla Bla bla
notify();//Trigger wait() Next Step
}
}
Threading中的wait()和notifyall()的示例。
同步的静态数组列表用作资源,如果数组列表为空,则调用wait()方法。为数组列表添加元素后,将调用notify()方法。
public class PrinterResource extends Thread{
//resource
public static List<String> arrayList = new ArrayList<String>();
public void addElement(String a){
//System.out.println("Add element method "+this.getName());
synchronized (arrayList) {
arrayList.add(a);
arrayList.notifyAll();
}
}
public void removeElement(){
//System.out.println("Remove element method "+this.getName());
synchronized (arrayList) {
if(arrayList.size() == 0){
try {
arrayList.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
arrayList.remove(0);
}
}
}
public void run(){
System.out.println("Thread name -- "+this.getName());
if(!this.getName().equalsIgnoreCase("p4")){
this.removeElement();
}
this.addElement("threads");
}
public static void main(String[] args) {
PrinterResource p1 = new PrinterResource();
p1.setName("p1");
p1.start();
PrinterResource p2 = new PrinterResource();
p2.setName("p2");
p2.start();
PrinterResource p3 = new PrinterResource();
p3.setName("p3");
p3.start();
PrinterResource p4 = new PrinterResource();
p4.setName("p4");
p4.start();
try{
p1.join();
p2.join();
p3.join();
p4.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Final size of arraylist "+arrayList.size());
}
}
if(arrayList.size() == 0)
,我认为这可能是一个错误。
notify
仅唤醒一个线程。如果两个使用者线程竞争删除一个元素,则一个notify可能会唤醒另一个使用者线程,后者无法对其进行任何操作,然后会进入睡眠状态(而不是我们希望插入的新生产者插入一个新元素。)生产者线程未唤醒,没有任何插入,现在所有三个线程将无限期休眠。我删除了先前的评论,因为它(错误地)说是虚假唤醒是问题的原因(不是)