5

我不精通多线程。我试图通过一个生产者线程重复截取屏幕截图,该线程将BufferedImage对象添加到ConcurrentLinkedQueue并且消费者线程将poll排队等待BufferedImage对象以将它们保存在文件中。我可以通过重复轮询(while 循环)来使用它们,但我不知道如何使用notify()and来使用它们wait()。我曾尝试在较小的程序中使用wait()and 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方法 assynchronised并尝试实现wait()and notify()。我做得对吗?请帮忙。谢谢。

4

3 回答 3

5

Queue在工作中使用了错误的方法。这ConcurrentLinkedQueue是一个非阻塞队列,这意味着没有生产者消费者语义。如果您只是在做一个阅读器和一个作者,请查看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 行,生产线程将把图像“放置”到队列中。我引用 place 因为 SynchrnousQueue 没有深度。实际发生的是线程告诉队列“如果有任何线程从这个队列中请求一个元素,那么给它那个线程让我继续。如果没有,我会等到另一个线程准备好”

第 //2 行类似于第 1 行,其中消费线程只是等待,直到有线程提供。这适用于单读者单作者

于 2012-01-11T20:12:21.097 回答
5

第一个问题是您在生产者中的不必要等待:

    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!");
                }
            }

应该看起来更像这样:

            }else{
                bufferedImage = queue.poll();

                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!");
                }
            }
于 2012-01-11T20:10:24.043 回答
4

一旦java.util.concurrent库进入 JDK1.5,编写自己的等待/通知逻辑的需求就消失了。在 2012 年,如果您正在做自己的等待/通知,那么您工作太努力了,应该强烈考虑尝试和真正的 java.util.concurrent 等价物。

话虽如此,我相信轮询是内置java.util.concurrent.ConcurrentLinkedQueue. 换句话说,消费者坐在他们自己的线程和来自 ConcurrentLinkedQue 的 .poll() 项目中,只要它是!isEmpty(). 我见过的大多数实现都会在!isEmpty(). 另外,请注意 Vint 家伙对我的回答的评论,.poll()可能会返回null。考虑其替代实现java.util.AbstractQueue可能具有更接近您正在寻找的阻塞行为。

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

最后,拿到 Goetz 的书《Java Concurrency In Practice》读一读。我几乎可以肯定它有一个配方可以用来替换您自己的本土等待/通知。

于 2012-01-11T20:09:44.103 回答