我不熟悉多线程。我试图通过一个生产者线程重复截取屏幕截图,它将BufferedImage
对象添加到ConcurrentLinkedQueue
,并且消费者线程将poll
队列为BufferedImage
对象以将它们保存在文件中。我可以通过重复轮询(while循环)来消耗它们,但我不知道如何使用notify()
和wait()
来消费它们。我尝试在较小的程序中使用wait()
和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()
。我做得对吗?请帮忙。感谢。
答案 0 :(得分: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!");
}
}
答案 1 :(得分: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!");
}
}
真的是这样。
让我解释一下。在第//行,生产线程将“放置”在队列上的图像。我引用的地方是因为SynchrnousQueue没有深度。实际发生的是线程告诉队列“如果有任何线程要求来自此队列的元素然后给它该线程并让我继续。如果不是,我会等到另一个线程准备好”
行// 2类似于1,其中消费线程只等待线程提供。这对单读者单作家
非常有用答案 2 :(得分:4)
一旦java.util.concurrent
库进入JDK1.5,就需要编写自己的等待/通知逻辑。在2012年,如果您正在进行自己的等待/通知,那么您工作太辛苦了,应该强烈考虑尝试和真实的java.util.concurrent等效项。
话虽如此,我相信民意调查是内置java.util.concurrent.ConcurrentLinkedQueue
背后的理念。换句话说,只要消息是!isEmpty()
,消费者就会坐在他们自己的ConcurrentLinkedQue中的Thread和.poll()项中。我见过的大多数实现都会在!isEmpty()
的测试之间抛出一些睡眠,但我不认为这实际上是必要的。另外,请注意Vint家伙对我的回答的评论,.poll()
可能会返回null
。考虑java.util.AbstractQueue
的替代实现,这些实现可能具有更接近您正在寻找的阻塞行为。
这家伙有一个简单的例子:http://www.informit.com/articles/article.aspx?p=1339471&seqNum=4
最后,获取Goetz的书“Java Concurrency In Practice”并阅读它。我几乎可以肯定它有一个用于替换你自己的本土等待/通知的方法。