2012-01-11 89 views
4

我不是很精通多线程。我想通过一个生产者线程,它增加了BufferedImage对象ConcurrentLinkedQueue和消费者线程将poll队列BufferedImage对象保存它们在文件采取截图反复。我可以重复投票(while循环)消耗他们,但我不知道如何使用notify()wait()消费它们。我已经尝试在较小的程序中使用wait()notify,但在此处无法实现。的ConcurrentLinkedQueue与等待()和notify()

我有以下代码:

class StartPeriodicTask implements Runnable { 
    public synchronized void run() { 
     Robot robot = null; 
     try { 
      robot = new Robot(); 
     } catch (AWTException e1) { 
      e1.printStackTrace(); 
     } 
     Rectangle screenRect = new Rectangle(Toolkit.getDefaultToolkit() 
       .getScreenSize()); 
     BufferedImage image = robot.createScreenCapture(screenRect); 
     if(null!=queue.peek()){ 
      try { 
       System.out.println("Empty queue, so waiting...."); 
       wait(); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     }else{ 
      queue.add(image); 
      notify(); 
     } 
    } 
} 

public class ImageConsumer implements Runnable { 
     @Override 
     public synchronized void run() { 
      while (true) { 
       BufferedImage bufferedImage = null; 
       if(null==queue.peek()){ 
        try { 
         //Empty queue, so waiting.... 
         wait(); 
        } catch (InterruptedException e) { 
         e.printStackTrace(); 
        } 
       }else{ 
        bufferedImage = queue.poll(); 
        notify(); 
       } 
       File imageFile = getFile(); 
       if (!imageFile.getParentFile().exists()) { 
        imageFile.getParentFile().mkdirs(); 
       } 
        try { 
         ImageIO.write(bufferedImage, extension, imageFile); 
         //Image saved 
        catch (IOException e) { 
         tracer.severe("IOException occurred. Image is not saved to file!"); 
        } 
       } 
      } 

以前我有一个重复投票,以检查BufferedImage对象的存在。现在,我已经改变了run方法synchronised,并试图实现wait()notify()。我做得对吗?请帮忙。谢谢。

回答

5

您正在使用错误的工作QueueConcurrentLinkedQueue是一个非阻塞队列,这意味着没有生产者消费者语义。如果你只是在做一个阅读器和一个作家看看SynchronousQueue

简单地说你的代码可以重新写成这样

BlockingQueue<?> queue = new SynchrnousQueue<?>(); 
class StartPeriodicTask implements Runnable { 
    public void run() { 
     Robot robot = null; 
     try { 
      robot = new Robot(); 
     } catch (AWTException e1) { 
      e1.printStackTrace(); 
     } 
     Rectangle screenRect = new Rectangle(Toolkit.getDefaultToolkit() 
       .getScreenSize()); 
     BufferedImage image = robot.createScreenCapture(screenRect); 
     queue.offer(image); //1 
} 
public class ImageConsumer implements Runnable { 
     @Override 
     public void run() { 
      while (true) { 
       BufferedImage bufferedImage = queue.poll(); //2 

       File imageFile = getFile(); 
       if (!imageFile.getParentFile().exists()) { 
        imageFile.getParentFile().mkdirs(); 
       } 
        try { 
         ImageIO.write(bufferedImage, extension, imageFile); 
         //Image saved 
        catch (IOException e) { 
         tracer.severe("IOException occurred. Image is not saved to file!"); 
        } 
      } 

这是真的了。

让我解释一下。在第// 1行,制作线程会将图像放置在队列中。我引用的地方是因为SynchrnousQueue没有深度。实际上发生的是线程告诉队列“如果有任何线程要求从这个队列中的元素,然后给它的线程,让我继续。如果没有,我会等到另一个线程准备好”

线// 2与消费线程一直等待线程提供时的类似。这对于建议不使用等待/通知的单读者单写入器

+0

非常好!它的工作!非常感谢! – Ahamed 2012-01-11 20:25:28

+0

单读者和单作者是什么意思?如果我们有多个线程提供和多个线程轮询会怎么样?那应该没问题吧? – Ahamed 2013-02-21 15:26:39

+0

@Ahamed是的,但我的意思是这是一个1-1的关系。对于放入队列的每个线程,您需要从队列中进行相应的线程轮询。线程不能放置在SynchrnousQueue上并继续运行,必须有一个线程要求一个元素 - 此时线程可以继续。 – 2013-02-21 15:39:20

4

一旦java.util.concurrent的图书馆走进JDK1.5,编写自己的等待/通知逻辑需要去正确的出了门。在2012年,如果你正在做自己的等待/通知,你工作太辛苦了,应该强烈考虑经过验证的和真正的java.util.concurrent等价物。

话虽这么说,我相信投票是背后的想法内置的java.util.concurrent.ConcurrentLinkedQueue中。换句话说,只要它是!isEmpty(),消费者就坐在自己的Thread和.poll()中的ConcurrentLinkedQue项目中。我所见过的大多数实现都在!isEmpty()的测试之间抛出了一秒钟的睡眠,但我认为实际上并不需要这样做。另外,请注意Vint人对我的回答的评论,.poll()可能会返回null。考虑一下java.util.AbstractQueue的替代实现,该实现可能具有更接近您正在寻找的阻塞行为。

这家伙有一个简单的例子:http://www.informit.com/articles/article.aspx?p=1339471&seqNum=4

最后,得到戈茨书的“Java并发编程实践”和阅读。我几乎可以肯定它有一个配方,用来替代你自己生产的wait/notifys。

+1

+1很有效。 – toto2 2012-01-11 20:20:13

+0

@Bob Kuhar'换句话说,消费者坐在他们自己的Thread中,并从ConcurrentLinkedQue中删除项目,只要它是!isEmpty()'这是不正确的。对于ArrayBlockingQueue和LinkedBlockingQueue而言,ConcurrentLinkedQueue.poll()将返回null,如果isEmpty() – 2012-01-11 20:20:16

+0

@JohnVint感谢您指出这一点。我改变了我的措辞,因为它不是“民意调查”方法,我说的是用isEmpty()的重复测试轮询队列的概念。 – 2012-01-11 20:31:01

4

第一个问题是您在制作有不必要的等待:

if(null!=queue.peek()){ // You are the producer, you don't care if the queue is empty 
     try { 
      System.out.println("Empty queue, so waiting...."); 
      wait(); // This puts you to bed, your waiting and so is your consumer 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    }else{ 
     queue.add(image); 
     notify(); 
    } 

这是你应该需要:

 queue.add(image); 
     notify(); 

下一个问题是你的消费者不必要notify。它可以控制它在那个时候的处理过程,我相信你的目的是让你的制作者走出去,但是当然你的代码从来没有达到过这个程度。所以这个:

  }else{ 
       bufferedImage = queue.poll(); 
       notify(); 
      } 
      File imageFile = getFile(); 
      if (!imageFile.getParentFile().exists()) { 
       imageFile.getParentFile().mkdirs(); 
      } 
       try { 
        ImageIO.write(bufferedImage, extension, imageFile); 
        //Image saved 
       catch (IOException e) { 
        tracer.severe("IOException occurred. Image is not saved to file!"); 
       } 
      } 

应该看起来更像是这样的:

​​